1#![allow(unused_variables, dead_code, unused_mut)]
2// todo!() this is to make transition easier.
3
4pub mod dock;
5pub mod item;
6pub mod notifications;
7pub mod pane;
8pub mod pane_group;
9mod persistence;
10pub mod searchable;
11// todo!()
12mod modal_layer;
13mod status_bar;
14mod toolbar;
15mod workspace_settings;
16
17use anyhow::{anyhow, Context as _, Result};
18use async_trait::async_trait;
19use client2::{
20 proto::{self, PeerId},
21 Client, TypedEnvelope, User, UserStore,
22};
23use collections::{hash_map, HashMap, HashSet};
24use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
25use futures::{
26 channel::{mpsc, oneshot},
27 future::try_join_all,
28 Future, FutureExt, StreamExt,
29};
30use gpui::{
31 actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
32 AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
33 FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
34 ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
35 View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext,
36 WindowHandle, WindowOptions,
37};
38use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
39use itertools::Itertools;
40use language2::{LanguageRegistry, Rope};
41use lazy_static::lazy_static;
42pub use modal_layer::*;
43use node_runtime::NodeRuntime;
44use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
45pub use pane::*;
46pub use pane_group::*;
47pub use persistence::{
48 model::{ItemId, SerializedWorkspace, WorkspaceLocation},
49 WorkspaceDb, DB,
50};
51use postage::stream::Stream;
52use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
53use serde::Deserialize;
54use settings2::Settings;
55use status_bar::StatusBar;
56pub use status_bar::StatusItemView;
57use std::{
58 any::TypeId,
59 borrow::Cow,
60 cmp, env,
61 path::{Path, PathBuf},
62 sync::{atomic::AtomicUsize, Arc},
63 time::Duration,
64};
65use theme2::{ActiveTheme, ThemeSettings};
66pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
67pub use ui;
68use util::ResultExt;
69use uuid::Uuid;
70pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
71
72use crate::persistence::model::{
73 DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
74};
75
76lazy_static! {
77 static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
78 .ok()
79 .as_deref()
80 .and_then(parse_pixel_size_env_var);
81 static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
82 .ok()
83 .as_deref()
84 .and_then(parse_pixel_position_env_var);
85}
86
87#[derive(Clone, PartialEq)]
88pub struct RemoveWorktreeFromProject(pub WorktreeId);
89
90actions!(
91 Open,
92 NewFile,
93 NewWindow,
94 CloseWindow,
95 CloseInactiveTabsAndPanes,
96 AddFolderToProject,
97 Unfollow,
98 SaveAs,
99 ReloadActiveItem,
100 ActivatePreviousPane,
101 ActivateNextPane,
102 FollowNextCollaborator,
103 NewTerminal,
104 NewCenterTerminal,
105 ToggleTerminalFocus,
106 NewSearch,
107 Feedback,
108 Restart,
109 Welcome,
110 ToggleZoom,
111 ToggleLeftDock,
112 ToggleRightDock,
113 ToggleBottomDock,
114 CloseAllDocks,
115);
116
117#[derive(Clone, PartialEq)]
118pub struct OpenPaths {
119 pub paths: Vec<PathBuf>,
120}
121
122#[derive(Clone, Deserialize, PartialEq, Action)]
123pub struct ActivatePane(pub usize);
124
125#[derive(Clone, Deserialize, PartialEq, Action)]
126pub struct ActivatePaneInDirection(pub SplitDirection);
127
128#[derive(Clone, Deserialize, PartialEq, Action)]
129pub struct SwapPaneInDirection(pub SplitDirection);
130
131#[derive(Clone, Deserialize, PartialEq, Action)]
132pub struct NewFileInDirection(pub SplitDirection);
133
134#[derive(Clone, PartialEq, Debug, Deserialize, Action)]
135#[serde(rename_all = "camelCase")]
136pub struct SaveAll {
137 pub save_intent: Option<SaveIntent>,
138}
139
140#[derive(Clone, PartialEq, Debug, Deserialize, Action)]
141#[serde(rename_all = "camelCase")]
142pub struct Save {
143 pub save_intent: Option<SaveIntent>,
144}
145
146#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)]
147#[serde(rename_all = "camelCase")]
148pub struct CloseAllItemsAndPanes {
149 pub save_intent: Option<SaveIntent>,
150}
151
152#[derive(Deserialize)]
153pub struct Toast {
154 id: usize,
155 msg: Cow<'static, str>,
156 #[serde(skip)]
157 on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
158}
159
160impl Toast {
161 pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
162 Toast {
163 id,
164 msg: msg.into(),
165 on_click: None,
166 }
167 }
168
169 pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
170 where
171 M: Into<Cow<'static, str>>,
172 F: Fn(&mut WindowContext) + 'static,
173 {
174 self.on_click = Some((message.into(), Arc::new(on_click)));
175 self
176 }
177}
178
179impl PartialEq for Toast {
180 fn eq(&self, other: &Self) -> bool {
181 self.id == other.id
182 && self.msg == other.msg
183 && self.on_click.is_some() == other.on_click.is_some()
184 }
185}
186
187impl Clone for Toast {
188 fn clone(&self) -> Self {
189 Toast {
190 id: self.id,
191 msg: self.msg.to_owned(),
192 on_click: self.on_click.clone(),
193 }
194 }
195}
196
197#[derive(Debug, Default, Clone, Deserialize, PartialEq, Action)]
198pub struct OpenTerminal {
199 pub working_directory: PathBuf,
200}
201
202pub type WorkspaceId = i64;
203
204pub fn init_settings(cx: &mut AppContext) {
205 WorkspaceSettings::register(cx);
206 ItemSettings::register(cx);
207}
208
209pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
210 init_settings(cx);
211 notifications::init(cx);
212 // cx.add_global_action({
213 // let app_state = Arc::downgrade(&app_state);
214 // move |_: &Open, cx: &mut AppContext| {
215 // let mut paths = cx.prompt_for_paths(PathPromptOptions {
216 // files: true,
217 // directories: true,
218 // multiple: true,
219 // });
220
221 // if let Some(app_state) = app_state.upgrade() {
222 // cx.spawn(move |mut cx| async move {
223 // if let Some(paths) = paths.recv().await.flatten() {
224 // cx.update(|cx| {
225 // open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
226 // });
227 // }
228 // })
229 // .detach();
230 // }
231 // }
232 // });
233}
234
235type ProjectItemBuilders =
236 HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
237pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
238 let builders = cx.default_global::<ProjectItemBuilders>();
239 builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
240 let item = model.downcast::<I::Item>().unwrap();
241 Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
242 });
243}
244
245type FollowableItemBuilder = fn(
246 View<Pane>,
247 View<Workspace>,
248 ViewId,
249 &mut Option<proto::view::Variant>,
250 &mut AppContext,
251) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
252type FollowableItemBuilders = HashMap<
253 TypeId,
254 (
255 FollowableItemBuilder,
256 fn(&AnyView) -> Box<dyn FollowableItemHandle>,
257 ),
258>;
259pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
260 let builders = cx.default_global::<FollowableItemBuilders>();
261 builders.insert(
262 TypeId::of::<I>(),
263 (
264 |pane, workspace, id, state, cx| {
265 I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
266 cx.foreground_executor()
267 .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
268 })
269 },
270 |this| Box::new(this.clone().downcast::<I>().unwrap()),
271 ),
272 );
273}
274
275type ItemDeserializers = HashMap<
276 Arc<str>,
277 fn(
278 Model<Project>,
279 WeakView<Workspace>,
280 WorkspaceId,
281 ItemId,
282 &mut ViewContext<Pane>,
283 ) -> Task<Result<Box<dyn ItemHandle>>>,
284>;
285pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
286 if let Some(serialized_item_kind) = I::serialized_item_kind() {
287 let deserializers = cx.default_global::<ItemDeserializers>();
288 deserializers.insert(
289 Arc::from(serialized_item_kind),
290 |project, workspace, workspace_id, item_id, cx| {
291 let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
292 cx.foreground_executor()
293 .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
294 },
295 );
296 }
297}
298
299pub struct AppState {
300 pub languages: Arc<LanguageRegistry>,
301 pub client: Arc<Client>,
302 pub user_store: Model<UserStore>,
303 pub workspace_store: Model<WorkspaceStore>,
304 pub fs: Arc<dyn fs2::Fs>,
305 pub call_factory: CallFactory,
306 pub build_window_options:
307 fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
308 pub node_runtime: Arc<dyn NodeRuntime>,
309}
310
311pub struct WorkspaceStore {
312 workspaces: HashSet<WindowHandle<Workspace>>,
313 followers: Vec<Follower>,
314 client: Arc<Client>,
315 _subscriptions: Vec<client2::Subscription>,
316}
317
318#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
319struct Follower {
320 project_id: Option<u64>,
321 peer_id: PeerId,
322}
323
324#[cfg(any(test, feature = "test-support"))]
325pub struct TestCallHandler;
326
327#[cfg(any(test, feature = "test-support"))]
328impl CallHandler for TestCallHandler {
329 fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)> {
330 None
331 }
332
333 fn shared_screen_for_peer(
334 &self,
335 peer_id: PeerId,
336 pane: &View<Pane>,
337 cx: &mut ViewContext<Workspace>,
338 ) -> Option<Box<dyn ItemHandle>> {
339 None
340 }
341
342 fn room_id(&self, cx: &AppContext) -> Option<u64> {
343 None
344 }
345
346 fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
347 Task::ready(Err(anyhow!("TestCallHandler should not be hanging up")))
348 }
349
350 fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
351 None
352 }
353
354 fn invite(
355 &mut self,
356 called_user_id: u64,
357 initial_project: Option<Model<Project>>,
358 cx: &mut AppContext,
359 ) -> Task<Result<()>> {
360 unimplemented!()
361 }
362
363 fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
364 None
365 }
366
367 fn is_muted(&self, cx: &AppContext) -> Option<bool> {
368 None
369 }
370
371 fn toggle_mute(&self, cx: &mut AppContext) {}
372
373 fn toggle_screen_share(&self, cx: &mut AppContext) {}
374
375 fn toggle_deafen(&self, cx: &mut AppContext) {}
376
377 fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
378 None
379 }
380}
381
382impl AppState {
383 #[cfg(any(test, feature = "test-support"))]
384 pub fn test(cx: &mut AppContext) -> Arc<Self> {
385 use node_runtime::FakeNodeRuntime;
386 use settings2::SettingsStore;
387
388 if !cx.has_global::<SettingsStore>() {
389 let settings_store = SettingsStore::test(cx);
390 cx.set_global(settings_store);
391 }
392
393 let fs = fs2::FakeFs::new(cx.background_executor().clone());
394 let languages = Arc::new(LanguageRegistry::test());
395 let http_client = util::http::FakeHttpClient::with_404_response();
396 let client = Client::new(http_client.clone(), cx);
397 let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
398 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
399
400 theme2::init(theme2::LoadThemes::JustBase, cx);
401 client2::init(&client, cx);
402 crate::init_settings(cx);
403
404 Arc::new(Self {
405 client,
406 fs,
407 languages,
408 user_store,
409 workspace_store,
410 node_runtime: FakeNodeRuntime::new(),
411 build_window_options: |_, _, _| Default::default(),
412 call_factory: |_, _| Box::new(TestCallHandler),
413 })
414 }
415}
416
417struct DelayedDebouncedEditAction {
418 task: Option<Task<()>>,
419 cancel_channel: Option<oneshot::Sender<()>>,
420}
421
422impl DelayedDebouncedEditAction {
423 fn new() -> DelayedDebouncedEditAction {
424 DelayedDebouncedEditAction {
425 task: None,
426 cancel_channel: None,
427 }
428 }
429
430 fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
431 where
432 F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
433 {
434 if let Some(channel) = self.cancel_channel.take() {
435 _ = channel.send(());
436 }
437
438 let (sender, mut receiver) = oneshot::channel::<()>();
439 self.cancel_channel = Some(sender);
440
441 let previous_task = self.task.take();
442 self.task = Some(cx.spawn(move |workspace, mut cx| async move {
443 let mut timer = cx.background_executor().timer(delay).fuse();
444 if let Some(previous_task) = previous_task {
445 previous_task.await;
446 }
447
448 futures::select_biased! {
449 _ = receiver => return,
450 _ = timer => {}
451 }
452
453 if let Some(result) = workspace
454 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
455 .log_err()
456 {
457 result.await.log_err();
458 }
459 }));
460 }
461}
462
463pub enum Event {
464 PaneAdded(View<Pane>),
465 ContactRequestedJoin(u64),
466 WorkspaceCreated(WeakView<Workspace>),
467}
468
469#[async_trait(?Send)]
470pub trait CallHandler {
471 fn peer_state(&mut self, id: PeerId, cx: &mut ViewContext<Workspace>) -> Option<(bool, bool)>;
472 fn shared_screen_for_peer(
473 &self,
474 peer_id: PeerId,
475 pane: &View<Pane>,
476 cx: &mut ViewContext<Workspace>,
477 ) -> Option<Box<dyn ItemHandle>>;
478 fn room_id(&self, cx: &AppContext) -> Option<u64>;
479 fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
480 self.room_id(cx).is_some()
481 }
482 fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>>;
483 fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
484 fn invite(
485 &mut self,
486 called_user_id: u64,
487 initial_project: Option<Model<Project>>,
488 cx: &mut AppContext,
489 ) -> Task<Result<()>>;
490 fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>>;
491 fn is_muted(&self, cx: &AppContext) -> Option<bool>;
492 fn is_deafened(&self, cx: &AppContext) -> Option<bool>;
493 fn toggle_mute(&self, cx: &mut AppContext);
494 fn toggle_deafen(&self, cx: &mut AppContext);
495 fn toggle_screen_share(&self, cx: &mut AppContext);
496}
497
498pub struct Workspace {
499 window_self: WindowHandle<Self>,
500 weak_self: WeakView<Self>,
501 workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
502 zoomed: Option<AnyWeakView>,
503 zoomed_position: Option<DockPosition>,
504 center: PaneGroup,
505 left_dock: View<Dock>,
506 bottom_dock: View<Dock>,
507 right_dock: View<Dock>,
508 panes: Vec<View<Pane>>,
509 panes_by_item: HashMap<EntityId, WeakView<Pane>>,
510 active_pane: View<Pane>,
511 last_active_center_pane: Option<WeakView<Pane>>,
512 last_active_view_id: Option<proto::ViewId>,
513 status_bar: View<StatusBar>,
514 modal_layer: View<ModalLayer>,
515 titlebar_item: Option<AnyView>,
516 notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
517 project: Model<Project>,
518 call_handler: Box<dyn CallHandler>,
519 follower_states: HashMap<View<Pane>, FollowerState>,
520 last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
521 window_edited: bool,
522 leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
523 database_id: WorkspaceId,
524 app_state: Arc<AppState>,
525 subscriptions: Vec<Subscription>,
526 _apply_leader_updates: Task<Result<()>>,
527 _observe_current_user: Task<Result<()>>,
528 _schedule_serialize: Option<Task<()>>,
529 pane_history_timestamp: Arc<AtomicUsize>,
530}
531
532impl EventEmitter<Event> for Workspace {}
533
534#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
535pub struct ViewId {
536 pub creator: PeerId,
537 pub id: u64,
538}
539
540#[derive(Default)]
541struct FollowerState {
542 leader_id: PeerId,
543 active_view_id: Option<ViewId>,
544 items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
545}
546
547enum WorkspaceBounds {}
548
549type CallFactory = fn(WeakView<Workspace>, &mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
550impl Workspace {
551 pub fn new(
552 workspace_id: WorkspaceId,
553 project: Model<Project>,
554 app_state: Arc<AppState>,
555 cx: &mut ViewContext<Self>,
556 ) -> Self {
557 cx.observe(&project, |_, _, cx| cx.notify()).detach();
558 cx.subscribe(&project, move |this, _, event, cx| {
559 match event {
560 project2::Event::RemoteIdChanged(_) => {
561 this.update_window_title(cx);
562 }
563
564 project2::Event::CollaboratorLeft(peer_id) => {
565 this.collaborator_left(*peer_id, cx);
566 }
567
568 project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
569 this.update_window_title(cx);
570 this.serialize_workspace(cx);
571 }
572
573 project2::Event::DisconnectedFromHost => {
574 this.update_window_edited(cx);
575 cx.blur();
576 }
577
578 project2::Event::Closed => {
579 cx.remove_window();
580 }
581
582 project2::Event::DeletedEntry(entry_id) => {
583 for pane in this.panes.iter() {
584 pane.update(cx, |pane, cx| {
585 pane.handle_deleted_project_item(*entry_id, cx)
586 });
587 }
588 }
589
590 project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
591 cx.build_view(|_| MessageNotification::new(message.clone()))
592 }),
593
594 _ => {}
595 }
596 cx.notify()
597 })
598 .detach();
599
600 let weak_handle = cx.view().downgrade();
601 let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
602
603 let center_pane = cx.build_view(|cx| {
604 Pane::new(
605 weak_handle.clone(),
606 project.clone(),
607 pane_history_timestamp.clone(),
608 cx,
609 )
610 });
611 cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
612
613 cx.focus_view(¢er_pane);
614 cx.emit(Event::PaneAdded(center_pane.clone()));
615
616 let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
617 app_state.workspace_store.update(cx, |store, _| {
618 store.workspaces.insert(window_handle);
619 });
620
621 let mut current_user = app_state.user_store.read(cx).watch_current_user();
622 let mut connection_status = app_state.client.status();
623 let _observe_current_user = cx.spawn(|this, mut cx| async move {
624 current_user.next().await;
625 connection_status.next().await;
626 let mut stream =
627 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
628
629 while stream.recv().await.is_some() {
630 this.update(&mut cx, |_, cx| cx.notify())?;
631 }
632 anyhow::Ok(())
633 });
634
635 // All leader updates are enqueued and then processed in a single task, so
636 // that each asynchronous operation can be run in order.
637 let (leader_updates_tx, mut leader_updates_rx) =
638 mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
639 let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
640 while let Some((leader_id, update)) = leader_updates_rx.next().await {
641 let mut cx2 = cx.clone();
642 let t = this.clone();
643
644 Workspace::process_leader_update(&this, leader_id, update, &mut cx)
645 .await
646 .log_err();
647
648 // this.update(&mut cx, |this, cxx| {
649 // this.call_handler
650 // .process_leader_update(leader_id, update, cx2)
651 // })?
652 // .await
653 // .log_err();
654 }
655
656 Ok(())
657 });
658
659 cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
660
661 let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
662 let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
663 let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
664 let left_dock_buttons =
665 cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
666 let bottom_dock_buttons =
667 cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
668 let right_dock_buttons =
669 cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
670 let status_bar = cx.build_view(|cx| {
671 let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
672 status_bar.add_left_item(left_dock_buttons, cx);
673 status_bar.add_right_item(right_dock_buttons, cx);
674 status_bar.add_right_item(bottom_dock_buttons, cx);
675 status_bar
676 });
677
678 let workspace_handle = cx.view().downgrade();
679 let modal_layer = cx.build_view(|cx| ModalLayer::new());
680
681 // todo!()
682 // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
683 // drag_and_drop.register_container(weak_handle.clone());
684 // });
685
686 let subscriptions = vec![
687 cx.observe_window_activation(Self::on_window_activation_changed),
688 cx.observe_window_bounds(move |_, cx| {
689 if let Some(display) = cx.display() {
690 // Transform fixed bounds to be stored in terms of the containing display
691 let mut bounds = cx.window_bounds();
692 if let WindowBounds::Fixed(window_bounds) = &mut bounds {
693 let display_bounds = display.bounds();
694 window_bounds.origin.x -= display_bounds.origin.x;
695 window_bounds.origin.y -= display_bounds.origin.y;
696 }
697
698 if let Some(display_uuid) = display.uuid().log_err() {
699 cx.background_executor()
700 .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
701 .detach_and_log_err(cx);
702 }
703 }
704 cx.notify();
705 }),
706 cx.observe(&left_dock, |this, _, cx| {
707 this.serialize_workspace(cx);
708 cx.notify();
709 }),
710 cx.observe(&bottom_dock, |this, _, cx| {
711 this.serialize_workspace(cx);
712 cx.notify();
713 }),
714 cx.observe(&right_dock, |this, _, cx| {
715 this.serialize_workspace(cx);
716 cx.notify();
717 }),
718 cx.on_release(|this, cx| {
719 this.app_state.workspace_store.update(cx, |store, _| {
720 store.workspaces.remove(&this.window_self);
721 })
722 }),
723 ];
724
725 cx.defer(|this, cx| {
726 this.update_window_title(cx);
727 // todo! @nate - these are useful for testing notifications
728 // this.show_error(
729 // &anyhow::anyhow!("what happens if this message is very very very very very long"),
730 // cx,
731 // );
732
733 // this.show_notification(1, cx, |cx| {
734 // cx.build_view(|_cx| {
735 // simple_message_notification::MessageNotification::new(format!("Error:"))
736 // .with_click_message("click here because!")
737 // })
738 // });
739 });
740 Workspace {
741 window_self: window_handle,
742 weak_self: weak_handle.clone(),
743 zoomed: None,
744 zoomed_position: None,
745 center: PaneGroup::new(center_pane.clone()),
746 panes: vec![center_pane.clone()],
747 panes_by_item: Default::default(),
748 active_pane: center_pane.clone(),
749 last_active_center_pane: Some(center_pane.downgrade()),
750 last_active_view_id: None,
751 status_bar,
752 modal_layer,
753 titlebar_item: None,
754 notifications: Default::default(),
755 left_dock,
756 bottom_dock,
757 right_dock,
758 project: project.clone(),
759 follower_states: Default::default(),
760 last_leaders_by_pane: Default::default(),
761 window_edited: false,
762
763 call_handler: (app_state.call_factory)(weak_handle.clone(), cx),
764 database_id: workspace_id,
765 app_state,
766 _observe_current_user,
767 _apply_leader_updates,
768 _schedule_serialize: None,
769 leader_updates_tx,
770 subscriptions,
771 pane_history_timestamp,
772 workspace_actions: Default::default(),
773 }
774 }
775
776 fn new_local(
777 abs_paths: Vec<PathBuf>,
778 app_state: Arc<AppState>,
779 requesting_window: Option<WindowHandle<Workspace>>,
780 cx: &mut AppContext,
781 ) -> Task<
782 anyhow::Result<(
783 WindowHandle<Workspace>,
784 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
785 )>,
786 > {
787 let project_handle = Project::local(
788 app_state.client.clone(),
789 app_state.node_runtime.clone(),
790 app_state.user_store.clone(),
791 app_state.languages.clone(),
792 app_state.fs.clone(),
793 cx,
794 );
795
796 cx.spawn(|mut cx| async move {
797 let serialized_workspace: Option<SerializedWorkspace> =
798 persistence::DB.workspace_for_roots(&abs_paths.as_slice());
799
800 let paths_to_open = Arc::new(abs_paths);
801
802 // Get project paths for all of the abs_paths
803 let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
804 let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
805 Vec::with_capacity(paths_to_open.len());
806 for path in paths_to_open.iter().cloned() {
807 if let Some((worktree, project_entry)) = cx
808 .update(|cx| {
809 Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
810 })?
811 .await
812 .log_err()
813 {
814 worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
815 project_paths.push((path, Some(project_entry)));
816 } else {
817 project_paths.push((path, None));
818 }
819 }
820
821 let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
822 serialized_workspace.id
823 } else {
824 DB.next_id().await.unwrap_or(0)
825 };
826
827 let window = if let Some(window) = requesting_window {
828 cx.update_window(window.into(), |old_workspace, cx| {
829 cx.replace_root_view(|cx| {
830 Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
831 });
832 })?;
833 window
834 } else {
835 let window_bounds_override = window_bounds_env_override(&cx);
836 let (bounds, display) = if let Some(bounds) = window_bounds_override {
837 (Some(bounds), None)
838 } else {
839 serialized_workspace
840 .as_ref()
841 .and_then(|serialized_workspace| {
842 let serialized_display = serialized_workspace.display?;
843 let mut bounds = serialized_workspace.bounds?;
844
845 // Stored bounds are relative to the containing display.
846 // So convert back to global coordinates if that screen still exists
847 if let WindowBounds::Fixed(mut window_bounds) = bounds {
848 let screen = cx
849 .update(|cx| {
850 cx.displays().into_iter().find(|display| {
851 display.uuid().ok() == Some(serialized_display)
852 })
853 })
854 .ok()??;
855 let screen_bounds = screen.bounds();
856 window_bounds.origin.x += screen_bounds.origin.x;
857 window_bounds.origin.y += screen_bounds.origin.y;
858 bounds = WindowBounds::Fixed(window_bounds);
859 }
860
861 Some((bounds, serialized_display))
862 })
863 .unzip()
864 };
865
866 // Use the serialized workspace to construct the new window
867 let options =
868 cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
869
870 cx.open_window(options, {
871 let app_state = app_state.clone();
872 let workspace_id = workspace_id.clone();
873 let project_handle = project_handle.clone();
874 move |cx| {
875 cx.build_view(|cx| {
876 Workspace::new(workspace_id, project_handle, app_state, cx)
877 })
878 }
879 })?
880 };
881
882 window
883 .update(&mut cx, |_, cx| cx.activate_window())
884 .log_err();
885
886 notify_if_database_failed(window, &mut cx);
887 let opened_items = window
888 .update(&mut cx, |_workspace, cx| {
889 open_items(serialized_workspace, project_paths, app_state, cx)
890 })?
891 .await
892 .unwrap_or_default();
893
894 Ok((window, opened_items))
895 })
896 }
897
898 pub fn weak_handle(&self) -> WeakView<Self> {
899 self.weak_self.clone()
900 }
901
902 pub fn left_dock(&self) -> &View<Dock> {
903 &self.left_dock
904 }
905
906 pub fn bottom_dock(&self) -> &View<Dock> {
907 &self.bottom_dock
908 }
909
910 pub fn right_dock(&self) -> &View<Dock> {
911 &self.right_dock
912 }
913
914 pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
915 let dock = match panel.position(cx) {
916 DockPosition::Left => &self.left_dock,
917 DockPosition::Bottom => &self.bottom_dock,
918 DockPosition::Right => &self.right_dock,
919 };
920
921 dock.update(cx, |dock, cx| {
922 dock.add_panel(panel, self.weak_self.clone(), cx)
923 });
924 }
925
926 pub fn status_bar(&self) -> &View<StatusBar> {
927 &self.status_bar
928 }
929
930 pub fn app_state(&self) -> &Arc<AppState> {
931 &self.app_state
932 }
933
934 pub fn user_store(&self) -> &Model<UserStore> {
935 &self.app_state.user_store
936 }
937
938 pub fn project(&self) -> &Model<Project> {
939 &self.project
940 }
941
942 pub fn recent_navigation_history(
943 &self,
944 limit: Option<usize>,
945 cx: &AppContext,
946 ) -> Vec<(ProjectPath, Option<PathBuf>)> {
947 let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
948 let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
949 for pane in &self.panes {
950 let pane = pane.read(cx);
951 pane.nav_history()
952 .for_each_entry(cx, |entry, (project_path, fs_path)| {
953 if let Some(fs_path) = &fs_path {
954 abs_paths_opened
955 .entry(fs_path.clone())
956 .or_default()
957 .insert(project_path.clone());
958 }
959 let timestamp = entry.timestamp;
960 match history.entry(project_path) {
961 hash_map::Entry::Occupied(mut entry) => {
962 let (_, old_timestamp) = entry.get();
963 if ×tamp > old_timestamp {
964 entry.insert((fs_path, timestamp));
965 }
966 }
967 hash_map::Entry::Vacant(entry) => {
968 entry.insert((fs_path, timestamp));
969 }
970 }
971 });
972 }
973
974 history
975 .into_iter()
976 .sorted_by_key(|(_, (_, timestamp))| *timestamp)
977 .map(|(project_path, (fs_path, _))| (project_path, fs_path))
978 .rev()
979 .filter(|(history_path, abs_path)| {
980 let latest_project_path_opened = abs_path
981 .as_ref()
982 .and_then(|abs_path| abs_paths_opened.get(abs_path))
983 .and_then(|project_paths| {
984 project_paths
985 .iter()
986 .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
987 });
988
989 match latest_project_path_opened {
990 Some(latest_project_path_opened) => latest_project_path_opened == history_path,
991 None => true,
992 }
993 })
994 .take(limit.unwrap_or(usize::MAX))
995 .collect()
996 }
997
998 fn navigate_history(
999 &mut self,
1000 pane: WeakView<Pane>,
1001 mode: NavigationMode,
1002 cx: &mut ViewContext<Workspace>,
1003 ) -> Task<Result<()>> {
1004 let to_load = if let Some(pane) = pane.upgrade() {
1005 // todo!("focus")
1006 // cx.focus(&pane);
1007
1008 pane.update(cx, |pane, cx| {
1009 loop {
1010 // Retrieve the weak item handle from the history.
1011 let entry = pane.nav_history_mut().pop(mode, cx)?;
1012
1013 // If the item is still present in this pane, then activate it.
1014 if let Some(index) = entry
1015 .item
1016 .upgrade()
1017 .and_then(|v| pane.index_for_item(v.as_ref()))
1018 {
1019 let prev_active_item_index = pane.active_item_index();
1020 pane.nav_history_mut().set_mode(mode);
1021 pane.activate_item(index, true, true, cx);
1022 pane.nav_history_mut().set_mode(NavigationMode::Normal);
1023
1024 let mut navigated = prev_active_item_index != pane.active_item_index();
1025 if let Some(data) = entry.data {
1026 navigated |= pane.active_item()?.navigate(data, cx);
1027 }
1028
1029 if navigated {
1030 break None;
1031 }
1032 }
1033 // If the item is no longer present in this pane, then retrieve its
1034 // project path in order to reopen it.
1035 else {
1036 break pane
1037 .nav_history()
1038 .path_for_item(entry.item.id())
1039 .map(|(project_path, _)| (project_path, entry));
1040 }
1041 }
1042 })
1043 } else {
1044 None
1045 };
1046
1047 if let Some((project_path, entry)) = to_load {
1048 // If the item was no longer present, then load it again from its previous path.
1049 let task = self.load_path(project_path, cx);
1050 cx.spawn(|workspace, mut cx| async move {
1051 let task = task.await;
1052 let mut navigated = false;
1053 if let Some((project_entry_id, build_item)) = task.log_err() {
1054 let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1055 pane.nav_history_mut().set_mode(mode);
1056 pane.active_item().map(|p| p.item_id())
1057 })?;
1058
1059 pane.update(&mut cx, |pane, cx| {
1060 let item = pane.open_item(project_entry_id, true, cx, build_item);
1061 navigated |= Some(item.item_id()) != prev_active_item_id;
1062 pane.nav_history_mut().set_mode(NavigationMode::Normal);
1063 if let Some(data) = entry.data {
1064 navigated |= item.navigate(data, cx);
1065 }
1066 })?;
1067 }
1068
1069 if !navigated {
1070 workspace
1071 .update(&mut cx, |workspace, cx| {
1072 Self::navigate_history(workspace, pane, mode, cx)
1073 })?
1074 .await?;
1075 }
1076
1077 Ok(())
1078 })
1079 } else {
1080 Task::ready(Ok(()))
1081 }
1082 }
1083
1084 pub fn go_back(
1085 &mut self,
1086 pane: WeakView<Pane>,
1087 cx: &mut ViewContext<Workspace>,
1088 ) -> Task<Result<()>> {
1089 self.navigate_history(pane, NavigationMode::GoingBack, cx)
1090 }
1091
1092 pub fn go_forward(
1093 &mut self,
1094 pane: WeakView<Pane>,
1095 cx: &mut ViewContext<Workspace>,
1096 ) -> Task<Result<()>> {
1097 self.navigate_history(pane, NavigationMode::GoingForward, cx)
1098 }
1099
1100 pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1101 self.navigate_history(
1102 self.active_pane().downgrade(),
1103 NavigationMode::ReopeningClosedItem,
1104 cx,
1105 )
1106 }
1107
1108 pub fn client(&self) -> &Client {
1109 &self.app_state.client
1110 }
1111
1112 pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
1113 self.titlebar_item = Some(item);
1114 cx.notify();
1115 }
1116
1117 pub fn titlebar_item(&self) -> Option<AnyView> {
1118 self.titlebar_item.clone()
1119 }
1120
1121 /// Call the given callback with a workspace whose project is local.
1122 ///
1123 /// If the given workspace has a local project, then it will be passed
1124 /// to the callback. Otherwise, a new empty window will be created.
1125 pub fn with_local_workspace<T, F>(
1126 &mut self,
1127 cx: &mut ViewContext<Self>,
1128 callback: F,
1129 ) -> Task<Result<T>>
1130 where
1131 T: 'static,
1132 F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1133 {
1134 if self.project.read(cx).is_local() {
1135 Task::Ready(Some(Ok(callback(self, cx))))
1136 } else {
1137 let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1138 cx.spawn(|_vh, mut cx| async move {
1139 let (workspace, _) = task.await?;
1140 workspace.update(&mut cx, callback)
1141 })
1142 }
1143 }
1144
1145 pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
1146 self.project.read(cx).worktrees()
1147 }
1148
1149 pub fn visible_worktrees<'a>(
1150 &self,
1151 cx: &'a AppContext,
1152 ) -> impl 'a + Iterator<Item = Model<Worktree>> {
1153 self.project.read(cx).visible_worktrees(cx)
1154 }
1155
1156 pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1157 let futures = self
1158 .worktrees(cx)
1159 .filter_map(|worktree| worktree.read(cx).as_local())
1160 .map(|worktree| worktree.scan_complete())
1161 .collect::<Vec<_>>();
1162 async move {
1163 for future in futures {
1164 future.await;
1165 }
1166 }
1167 }
1168
1169 // todo!(Non-window-actions)
1170 pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1171 cx.windows().iter().find(|window| {
1172 window
1173 .update(cx, |_, window| {
1174 if window.is_window_active() {
1175 //This can only get called when the window's project connection has been lost
1176 //so we don't need to prompt the user for anything and instead just close the window
1177 window.remove_window();
1178 true
1179 } else {
1180 false
1181 }
1182 })
1183 .unwrap_or(false)
1184 });
1185 }
1186
1187 pub fn close(
1188 &mut self,
1189 _: &CloseWindow,
1190 cx: &mut ViewContext<Self>,
1191 ) -> Option<Task<Result<()>>> {
1192 let window = cx.window_handle();
1193 let prepare = self.prepare_to_close(false, cx);
1194 Some(cx.spawn(|_, mut cx| async move {
1195 if prepare.await? {
1196 window.update(&mut cx, |_, cx| {
1197 cx.remove_window();
1198 })?;
1199 }
1200 Ok(())
1201 }))
1202 }
1203
1204 pub fn prepare_to_close(
1205 &mut self,
1206 quitting: bool,
1207 cx: &mut ViewContext<Self>,
1208 ) -> Task<Result<bool>> {
1209 //todo!(saveing)
1210
1211 let window = cx.window_handle();
1212
1213 cx.spawn(|this, mut cx| async move {
1214 let workspace_count = cx.update(|_, cx| {
1215 cx.windows()
1216 .iter()
1217 .filter(|window| window.downcast::<Workspace>().is_some())
1218 .count()
1219 })?;
1220
1221 if !quitting
1222 && workspace_count == 1
1223 && this
1224 .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx))
1225 .log_err()
1226 .unwrap_or_default()
1227 {
1228 let answer = window.update(&mut cx, |_, cx| {
1229 cx.prompt(
1230 PromptLevel::Warning,
1231 "Do you want to leave the current call?",
1232 &["Close window and hang up", "Cancel"],
1233 )
1234 })?;
1235
1236 if answer.await.log_err() == Some(1) {
1237 return anyhow::Ok(false);
1238 } else {
1239 this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))?
1240 .await
1241 .log_err();
1242 }
1243 }
1244
1245 Ok(this
1246 .update(&mut cx, |this, cx| {
1247 this.save_all_internal(SaveIntent::Close, cx)
1248 })?
1249 .await?)
1250 })
1251 }
1252
1253 fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
1254 let save_all = self
1255 .save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
1256 .detach_and_log_err(cx);
1257 }
1258
1259 fn save_all_internal(
1260 &mut self,
1261 mut save_intent: SaveIntent,
1262 cx: &mut ViewContext<Self>,
1263 ) -> Task<Result<bool>> {
1264 if self.project.read(cx).is_read_only() {
1265 return Task::ready(Ok(true));
1266 }
1267 let dirty_items = self
1268 .panes
1269 .iter()
1270 .flat_map(|pane| {
1271 pane.read(cx).items().filter_map(|item| {
1272 if item.is_dirty(cx) {
1273 Some((pane.downgrade(), item.boxed_clone()))
1274 } else {
1275 None
1276 }
1277 })
1278 })
1279 .collect::<Vec<_>>();
1280
1281 let project = self.project.clone();
1282 cx.spawn(|workspace, mut cx| async move {
1283 // Override save mode and display "Save all files" prompt
1284 if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
1285 let mut answer = workspace.update(&mut cx, |_, cx| {
1286 let prompt = Pane::file_names_for_prompt(
1287 &mut dirty_items.iter().map(|(_, handle)| handle),
1288 dirty_items.len(),
1289 cx,
1290 );
1291 cx.prompt(
1292 PromptLevel::Warning,
1293 &prompt,
1294 &["Save all", "Discard all", "Cancel"],
1295 )
1296 })?;
1297 match answer.await.log_err() {
1298 Some(0) => save_intent = SaveIntent::SaveAll,
1299 Some(1) => save_intent = SaveIntent::Skip,
1300 _ => {}
1301 }
1302 }
1303 for (pane, item) in dirty_items {
1304 let (singleton, project_entry_ids) =
1305 cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
1306 if singleton || !project_entry_ids.is_empty() {
1307 if let Some(ix) =
1308 pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
1309 {
1310 if !Pane::save_item(
1311 project.clone(),
1312 &pane,
1313 ix,
1314 &*item,
1315 save_intent,
1316 &mut cx,
1317 )
1318 .await?
1319 {
1320 return Ok(false);
1321 }
1322 }
1323 }
1324 }
1325 Ok(true)
1326 })
1327 }
1328
1329 pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
1330 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1331 files: true,
1332 directories: true,
1333 multiple: true,
1334 });
1335
1336 cx.spawn(|this, mut cx| async move {
1337 let Some(paths) = paths.await.log_err().flatten() else {
1338 return;
1339 };
1340
1341 if let Some(task) = this
1342 .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1343 .log_err()
1344 {
1345 task.await.log_err();
1346 }
1347 })
1348 .detach()
1349 }
1350
1351 pub fn open_workspace_for_paths(
1352 &mut self,
1353 paths: Vec<PathBuf>,
1354 cx: &mut ViewContext<Self>,
1355 ) -> Task<Result<()>> {
1356 let window = cx.window_handle().downcast::<Self>();
1357 let is_remote = self.project.read(cx).is_remote();
1358 let has_worktree = self.project.read(cx).worktrees().next().is_some();
1359 let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1360 let close_task = if is_remote || has_worktree || has_dirty_items {
1361 None
1362 } else {
1363 Some(self.prepare_to_close(false, cx))
1364 };
1365 let app_state = self.app_state.clone();
1366
1367 cx.spawn(|_, mut cx| async move {
1368 let window_to_replace = if let Some(close_task) = close_task {
1369 if !close_task.await? {
1370 return Ok(());
1371 }
1372 window
1373 } else {
1374 None
1375 };
1376 cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))?
1377 .await?;
1378 Ok(())
1379 })
1380 }
1381
1382 #[allow(clippy::type_complexity)]
1383 pub fn open_paths(
1384 &mut self,
1385 mut abs_paths: Vec<PathBuf>,
1386 visible: bool,
1387 cx: &mut ViewContext<Self>,
1388 ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1389 log::info!("open paths {abs_paths:?}");
1390
1391 let fs = self.app_state.fs.clone();
1392
1393 // Sort the paths to ensure we add worktrees for parents before their children.
1394 abs_paths.sort_unstable();
1395 cx.spawn(move |this, mut cx| async move {
1396 let mut tasks = Vec::with_capacity(abs_paths.len());
1397 for abs_path in &abs_paths {
1398 let project_path = match this
1399 .update(&mut cx, |this, cx| {
1400 Workspace::project_path_for_path(
1401 this.project.clone(),
1402 abs_path,
1403 visible,
1404 cx,
1405 )
1406 })
1407 .log_err()
1408 {
1409 Some(project_path) => project_path.await.log_err(),
1410 None => None,
1411 };
1412
1413 let this = this.clone();
1414 let abs_path = abs_path.clone();
1415 let fs = fs.clone();
1416 let task = cx.spawn(move |mut cx| async move {
1417 let (worktree, project_path) = project_path?;
1418 if fs.is_file(&abs_path).await {
1419 Some(
1420 this.update(&mut cx, |this, cx| {
1421 this.open_path(project_path, None, true, cx)
1422 })
1423 .log_err()?
1424 .await,
1425 )
1426 } else {
1427 this.update(&mut cx, |workspace, cx| {
1428 let worktree = worktree.read(cx);
1429 let worktree_abs_path = worktree.abs_path();
1430 let entry_id = if abs_path == worktree_abs_path.as_ref() {
1431 worktree.root_entry()
1432 } else {
1433 abs_path
1434 .strip_prefix(worktree_abs_path.as_ref())
1435 .ok()
1436 .and_then(|relative_path| {
1437 worktree.entry_for_path(relative_path)
1438 })
1439 }
1440 .map(|entry| entry.id);
1441 if let Some(entry_id) = entry_id {
1442 workspace.project.update(cx, |_, cx| {
1443 cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
1444 })
1445 }
1446 })
1447 .log_err()?;
1448 None
1449 }
1450 });
1451 tasks.push(task);
1452 }
1453
1454 futures::future::join_all(tasks).await
1455 })
1456 }
1457
1458 fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1459 let mut paths = cx.prompt_for_paths(PathPromptOptions {
1460 files: false,
1461 directories: true,
1462 multiple: true,
1463 });
1464 cx.spawn(|this, mut cx| async move {
1465 if let Some(paths) = paths.await.log_err().flatten() {
1466 let results = this
1467 .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1468 .await;
1469 for result in results.into_iter().flatten() {
1470 result.log_err();
1471 }
1472 }
1473 anyhow::Ok(())
1474 })
1475 .detach_and_log_err(cx);
1476 }
1477
1478 fn project_path_for_path(
1479 project: Model<Project>,
1480 abs_path: &Path,
1481 visible: bool,
1482 cx: &mut AppContext,
1483 ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
1484 let entry = project.update(cx, |project, cx| {
1485 project.find_or_create_local_worktree(abs_path, visible, cx)
1486 });
1487 cx.spawn(|mut cx| async move {
1488 let (worktree, path) = entry.await?;
1489 let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
1490 Ok((
1491 worktree,
1492 ProjectPath {
1493 worktree_id,
1494 path: path.into(),
1495 },
1496 ))
1497 })
1498 }
1499
1500 pub fn items<'a>(
1501 &'a self,
1502 cx: &'a AppContext,
1503 ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1504 self.panes.iter().flat_map(|pane| pane.read(cx).items())
1505 }
1506
1507 pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
1508 self.items_of_type(cx).max_by_key(|item| item.item_id())
1509 }
1510
1511 pub fn items_of_type<'a, T: Item>(
1512 &'a self,
1513 cx: &'a AppContext,
1514 ) -> impl 'a + Iterator<Item = View<T>> {
1515 self.panes
1516 .iter()
1517 .flat_map(|pane| pane.read(cx).items_of_type())
1518 }
1519
1520 pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1521 self.active_pane().read(cx).active_item()
1522 }
1523
1524 pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
1525 let item = self.active_item(cx)?;
1526 item.to_any().downcast::<I>().ok()
1527 }
1528
1529 fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1530 self.active_item(cx).and_then(|item| item.project_path(cx))
1531 }
1532
1533 pub fn save_active_item(
1534 &mut self,
1535 save_intent: SaveIntent,
1536 cx: &mut ViewContext<Self>,
1537 ) -> Task<Result<()>> {
1538 let project = self.project.clone();
1539 let pane = self.active_pane();
1540 let item_ix = pane.read(cx).active_item_index();
1541 let item = pane.read(cx).active_item();
1542 let pane = pane.downgrade();
1543
1544 cx.spawn(|_, mut cx| async move {
1545 if let Some(item) = item {
1546 Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
1547 .await
1548 .map(|_| ())
1549 } else {
1550 Ok(())
1551 }
1552 })
1553 }
1554
1555 pub fn close_inactive_items_and_panes(
1556 &mut self,
1557 _: &CloseInactiveTabsAndPanes,
1558 cx: &mut ViewContext<Self>,
1559 ) {
1560 self.close_all_internal(true, SaveIntent::Close, cx)
1561 .map(|task| task.detach_and_log_err(cx));
1562 }
1563
1564 pub fn close_all_items_and_panes(
1565 &mut self,
1566 action: &CloseAllItemsAndPanes,
1567 cx: &mut ViewContext<Self>,
1568 ) {
1569 self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
1570 .map(|task| task.detach_and_log_err(cx));
1571 }
1572
1573 fn close_all_internal(
1574 &mut self,
1575 retain_active_pane: bool,
1576 save_intent: SaveIntent,
1577 cx: &mut ViewContext<Self>,
1578 ) -> Option<Task<Result<()>>> {
1579 let current_pane = self.active_pane();
1580
1581 let mut tasks = Vec::new();
1582
1583 if retain_active_pane {
1584 if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
1585 pane.close_inactive_items(&CloseInactiveItems, cx)
1586 }) {
1587 tasks.push(current_pane_close);
1588 };
1589 }
1590
1591 for pane in self.panes() {
1592 if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
1593 continue;
1594 }
1595
1596 if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
1597 pane.close_all_items(
1598 &CloseAllItems {
1599 save_intent: Some(save_intent),
1600 },
1601 cx,
1602 )
1603 }) {
1604 tasks.push(close_pane_items)
1605 }
1606 }
1607
1608 if tasks.is_empty() {
1609 None
1610 } else {
1611 Some(cx.spawn(|_, _| async move {
1612 for task in tasks {
1613 task.await?
1614 }
1615 Ok(())
1616 }))
1617 }
1618 }
1619
1620 pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1621 let dock = match dock_side {
1622 DockPosition::Left => &self.left_dock,
1623 DockPosition::Bottom => &self.bottom_dock,
1624 DockPosition::Right => &self.right_dock,
1625 };
1626 let mut focus_center = false;
1627 let mut reveal_dock = false;
1628 dock.update(cx, |dock, cx| {
1629 let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1630 let was_visible = dock.is_open() && !other_is_zoomed;
1631 dock.set_open(!was_visible, cx);
1632
1633 if let Some(active_panel) = dock.active_panel() {
1634 if was_visible {
1635 if active_panel.has_focus(cx) {
1636 focus_center = true;
1637 }
1638 } else {
1639 let focus_handle = &active_panel.focus_handle(cx);
1640 cx.focus(focus_handle);
1641 reveal_dock = true;
1642 }
1643 }
1644 });
1645
1646 if reveal_dock {
1647 self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1648 }
1649
1650 if focus_center {
1651 self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1652 }
1653
1654 cx.notify();
1655 self.serialize_workspace(cx);
1656 }
1657
1658 pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
1659 let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
1660
1661 for dock in docks {
1662 dock.update(cx, |dock, cx| {
1663 dock.set_open(false, cx);
1664 });
1665 }
1666
1667 // todo!("focus")
1668 // cx.focus_self();
1669 cx.notify();
1670 self.serialize_workspace(cx);
1671 }
1672
1673 /// Transfer focus to the panel of the given type.
1674 pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
1675 let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
1676 panel.to_any().downcast().ok()
1677 }
1678
1679 /// Focus the panel of the given type if it isn't already focused. If it is
1680 /// already focused, then transfer focus back to the workspace center.
1681 pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1682 self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1683 }
1684
1685 /// Focus or unfocus the given panel type, depending on the given callback.
1686 fn focus_or_unfocus_panel<T: Panel>(
1687 &mut self,
1688 cx: &mut ViewContext<Self>,
1689 should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1690 ) -> Option<Arc<dyn PanelHandle>> {
1691 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1692 if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1693 let mut focus_center = false;
1694 let mut reveal_dock = false;
1695 let panel = dock.update(cx, |dock, cx| {
1696 dock.activate_panel(panel_index, cx);
1697
1698 let panel = dock.active_panel().cloned();
1699 if let Some(panel) = panel.as_ref() {
1700 if should_focus(&**panel, cx) {
1701 dock.set_open(true, cx);
1702 panel.focus_handle(cx).focus(cx);
1703 reveal_dock = true;
1704 } else {
1705 // if panel.is_zoomed(cx) {
1706 // dock.set_open(false, cx);
1707 // }
1708 focus_center = true;
1709 }
1710 }
1711 panel
1712 });
1713
1714 if focus_center {
1715 self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1716 }
1717
1718 self.serialize_workspace(cx);
1719 cx.notify();
1720 return panel;
1721 }
1722 }
1723 None
1724 }
1725
1726 pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1727 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1728 let dock = dock.read(cx);
1729 if let Some(panel) = dock.panel::<T>() {
1730 return Some(panel);
1731 }
1732 }
1733 None
1734 }
1735
1736 fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1737 for pane in &self.panes {
1738 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1739 }
1740
1741 self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1742 self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1743 self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1744 self.zoomed = None;
1745 self.zoomed_position = None;
1746
1747 cx.notify();
1748 }
1749
1750 // #[cfg(any(test, feature = "test-support"))]
1751 // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1752 // self.zoomed.and_then(|view| view.upgrade(cx))
1753 // }
1754
1755 fn dismiss_zoomed_items_to_reveal(
1756 &mut self,
1757 dock_to_reveal: Option<DockPosition>,
1758 cx: &mut ViewContext<Self>,
1759 ) {
1760 // If a center pane is zoomed, unzoom it.
1761 for pane in &self.panes {
1762 if pane != &self.active_pane || dock_to_reveal.is_some() {
1763 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1764 }
1765 }
1766
1767 // If another dock is zoomed, hide it.
1768 let mut focus_center = false;
1769 for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1770 dock.update(cx, |dock, cx| {
1771 if Some(dock.position()) != dock_to_reveal {
1772 if let Some(panel) = dock.active_panel() {
1773 if panel.is_zoomed(cx) {
1774 focus_center |= panel.has_focus(cx);
1775 dock.set_open(false, cx);
1776 }
1777 }
1778 }
1779 });
1780 }
1781
1782 if focus_center {
1783 self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1784 }
1785
1786 if self.zoomed_position != dock_to_reveal {
1787 self.zoomed = None;
1788 self.zoomed_position = None;
1789 }
1790
1791 cx.notify();
1792 }
1793
1794 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1795 let pane = cx.build_view(|cx| {
1796 Pane::new(
1797 self.weak_handle(),
1798 self.project.clone(),
1799 self.pane_history_timestamp.clone(),
1800 cx,
1801 )
1802 });
1803 cx.subscribe(&pane, Self::handle_pane_event).detach();
1804 self.panes.push(pane.clone());
1805 // todo!()
1806 // cx.focus(&pane);
1807 cx.emit(Event::PaneAdded(pane.clone()));
1808 pane
1809 }
1810
1811 pub fn add_item_to_center(
1812 &mut self,
1813 item: Box<dyn ItemHandle>,
1814 cx: &mut ViewContext<Self>,
1815 ) -> bool {
1816 if let Some(center_pane) = self.last_active_center_pane.clone() {
1817 if let Some(center_pane) = center_pane.upgrade() {
1818 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1819 true
1820 } else {
1821 false
1822 }
1823 } else {
1824 false
1825 }
1826 }
1827
1828 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1829 self.active_pane
1830 .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1831 }
1832
1833 pub fn split_item(
1834 &mut self,
1835 split_direction: SplitDirection,
1836 item: Box<dyn ItemHandle>,
1837 cx: &mut ViewContext<Self>,
1838 ) {
1839 let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1840 new_pane.update(cx, move |new_pane, cx| {
1841 new_pane.add_item(item, true, true, None, cx)
1842 })
1843 }
1844
1845 pub fn open_abs_path(
1846 &mut self,
1847 abs_path: PathBuf,
1848 visible: bool,
1849 cx: &mut ViewContext<Self>,
1850 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1851 cx.spawn(|workspace, mut cx| async move {
1852 let open_paths_task_result = workspace
1853 .update(&mut cx, |workspace, cx| {
1854 workspace.open_paths(vec![abs_path.clone()], visible, cx)
1855 })
1856 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1857 .await;
1858 anyhow::ensure!(
1859 open_paths_task_result.len() == 1,
1860 "open abs path {abs_path:?} task returned incorrect number of results"
1861 );
1862 match open_paths_task_result
1863 .into_iter()
1864 .next()
1865 .expect("ensured single task result")
1866 {
1867 Some(open_result) => {
1868 open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1869 }
1870 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1871 }
1872 })
1873 }
1874
1875 pub fn split_abs_path(
1876 &mut self,
1877 abs_path: PathBuf,
1878 visible: bool,
1879 cx: &mut ViewContext<Self>,
1880 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1881 let project_path_task =
1882 Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1883 cx.spawn(|this, mut cx| async move {
1884 let (_, path) = project_path_task.await?;
1885 this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1886 .await
1887 })
1888 }
1889
1890 pub fn open_path(
1891 &mut self,
1892 path: impl Into<ProjectPath>,
1893 pane: Option<WeakView<Pane>>,
1894 focus_item: bool,
1895 cx: &mut ViewContext<Self>,
1896 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1897 let pane = pane.unwrap_or_else(|| {
1898 self.last_active_center_pane.clone().unwrap_or_else(|| {
1899 self.panes
1900 .first()
1901 .expect("There must be an active pane")
1902 .downgrade()
1903 })
1904 });
1905
1906 let task = self.load_path(path.into(), cx);
1907 cx.spawn(move |_, mut cx| async move {
1908 let (project_entry_id, build_item) = task.await?;
1909 pane.update(&mut cx, |pane, cx| {
1910 pane.open_item(project_entry_id, focus_item, cx, build_item)
1911 })
1912 })
1913 }
1914
1915 pub fn split_path(
1916 &mut self,
1917 path: impl Into<ProjectPath>,
1918 cx: &mut ViewContext<Self>,
1919 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1920 let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1921 self.panes
1922 .first()
1923 .expect("There must be an active pane")
1924 .downgrade()
1925 });
1926
1927 if let Member::Pane(center_pane) = &self.center.root {
1928 if center_pane.read(cx).items_len() == 0 {
1929 return self.open_path(path, Some(pane), true, cx);
1930 }
1931 }
1932
1933 let task = self.load_path(path.into(), cx);
1934 cx.spawn(|this, mut cx| async move {
1935 let (project_entry_id, build_item) = task.await?;
1936 this.update(&mut cx, move |this, cx| -> Option<_> {
1937 let pane = pane.upgrade()?;
1938 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1939 new_pane.update(cx, |new_pane, cx| {
1940 Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1941 })
1942 })
1943 .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1944 })
1945 }
1946
1947 pub(crate) fn load_path(
1948 &mut self,
1949 path: ProjectPath,
1950 cx: &mut ViewContext<Self>,
1951 ) -> Task<
1952 Result<(
1953 ProjectEntryId,
1954 impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1955 )>,
1956 > {
1957 let project = self.project().clone();
1958 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1959 cx.spawn(|_, mut cx| async move {
1960 let (project_entry_id, project_item) = project_item.await?;
1961 let build_item = cx.update(|_, cx| {
1962 cx.default_global::<ProjectItemBuilders>()
1963 .get(&project_item.entity_type())
1964 .ok_or_else(|| anyhow!("no item builder for project item"))
1965 .cloned()
1966 })??;
1967 let build_item =
1968 move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1969 Ok((project_entry_id, build_item))
1970 })
1971 }
1972
1973 pub fn open_project_item<T>(
1974 &mut self,
1975 project_item: Model<T::Item>,
1976 cx: &mut ViewContext<Self>,
1977 ) -> View<T>
1978 where
1979 T: ProjectItem,
1980 {
1981 use project2::Item as _;
1982
1983 let entry_id = project_item.read(cx).entry_id(cx);
1984 if let Some(item) = entry_id
1985 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1986 .and_then(|item| item.downcast())
1987 {
1988 self.activate_item(&item, cx);
1989 return item;
1990 }
1991
1992 let item =
1993 cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1994 self.add_item(Box::new(item.clone()), cx);
1995 item
1996 }
1997
1998 pub fn split_project_item<T>(
1999 &mut self,
2000 project_item: Model<T::Item>,
2001 cx: &mut ViewContext<Self>,
2002 ) -> View<T>
2003 where
2004 T: ProjectItem,
2005 {
2006 use project2::Item as _;
2007
2008 let entry_id = project_item.read(cx).entry_id(cx);
2009 if let Some(item) = entry_id
2010 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
2011 .and_then(|item| item.downcast())
2012 {
2013 self.activate_item(&item, cx);
2014 return item;
2015 }
2016
2017 let item =
2018 cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2019 self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
2020 item
2021 }
2022
2023 pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2024 if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2025 self.active_pane.update(cx, |pane, cx| {
2026 pane.add_item(shared_screen, false, true, None, cx)
2027 });
2028 }
2029 }
2030
2031 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2032 let result = self.panes.iter().find_map(|pane| {
2033 pane.read(cx)
2034 .index_for_item(item)
2035 .map(|ix| (pane.clone(), ix))
2036 });
2037 if let Some((pane, ix)) = result {
2038 pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2039 true
2040 } else {
2041 false
2042 }
2043 }
2044
2045 fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2046 let panes = self.center.panes();
2047 if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2048 cx.focus_view(&pane);
2049 } else {
2050 self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2051 }
2052 }
2053
2054 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2055 let panes = self.center.panes();
2056 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2057 let next_ix = (ix + 1) % panes.len();
2058 let next_pane = panes[next_ix].clone();
2059 cx.focus_view(&next_pane);
2060 }
2061 }
2062
2063 pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2064 let panes = self.center.panes();
2065 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2066 let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2067 let prev_pane = panes[prev_ix].clone();
2068 cx.focus_view(&prev_pane);
2069 }
2070 }
2071
2072 pub fn activate_pane_in_direction(
2073 &mut self,
2074 direction: SplitDirection,
2075 cx: &mut ViewContext<Self>,
2076 ) {
2077 if let Some(pane) = self.find_pane_in_direction(direction, cx) {
2078 cx.focus_view(pane);
2079 }
2080 }
2081
2082 pub fn swap_pane_in_direction(
2083 &mut self,
2084 direction: SplitDirection,
2085 cx: &mut ViewContext<Self>,
2086 ) {
2087 if let Some(to) = self
2088 .find_pane_in_direction(direction, cx)
2089 .map(|pane| pane.clone())
2090 {
2091 self.center.swap(&self.active_pane.clone(), &to);
2092 cx.notify();
2093 }
2094 }
2095
2096 fn find_pane_in_direction(
2097 &mut self,
2098 direction: SplitDirection,
2099 cx: &mut ViewContext<Self>,
2100 ) -> Option<&View<Pane>> {
2101 let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2102 return None;
2103 };
2104 let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2105 let center = match cursor {
2106 Some(cursor) if bounding_box.contains_point(&cursor) => cursor,
2107 _ => bounding_box.center(),
2108 };
2109
2110 let distance_to_next = 1.; //todo(pane dividers styling)
2111
2112 let target = match direction {
2113 SplitDirection::Left => {
2114 Point::new(bounding_box.origin.x - distance_to_next.into(), center.y)
2115 }
2116 SplitDirection::Right => {
2117 Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2118 }
2119 SplitDirection::Up => {
2120 Point::new(center.x, bounding_box.origin.y - distance_to_next.into())
2121 }
2122 SplitDirection::Down => {
2123 Point::new(center.x, bounding_box.top() + distance_to_next.into())
2124 }
2125 };
2126 self.center.pane_at_pixel_position(target)
2127 }
2128
2129 fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2130 if self.active_pane != pane {
2131 self.active_pane = pane.clone();
2132 self.status_bar.update(cx, |status_bar, cx| {
2133 status_bar.set_active_pane(&self.active_pane, cx);
2134 });
2135 self.active_item_path_changed(cx);
2136 self.last_active_center_pane = Some(pane.downgrade());
2137 }
2138
2139 self.dismiss_zoomed_items_to_reveal(None, cx);
2140 if pane.read(cx).is_zoomed() {
2141 self.zoomed = Some(pane.downgrade().into());
2142 } else {
2143 self.zoomed = None;
2144 }
2145 self.zoomed_position = None;
2146 self.update_active_view_for_followers(cx);
2147
2148 cx.notify();
2149 }
2150
2151 fn handle_pane_event(
2152 &mut self,
2153 pane: View<Pane>,
2154 event: &pane::Event,
2155 cx: &mut ViewContext<Self>,
2156 ) {
2157 match event {
2158 pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2159 pane::Event::Split(direction) => {
2160 self.split_and_clone(pane, *direction, cx);
2161 }
2162 pane::Event::Remove => self.remove_pane(pane, cx),
2163 pane::Event::ActivateItem { local } => {
2164 if *local {
2165 self.unfollow(&pane, cx);
2166 }
2167 if &pane == self.active_pane() {
2168 self.active_item_path_changed(cx);
2169 }
2170 }
2171 pane::Event::ChangeItemTitle => {
2172 if pane == self.active_pane {
2173 self.active_item_path_changed(cx);
2174 }
2175 self.update_window_edited(cx);
2176 }
2177 pane::Event::RemoveItem { item_id } => {
2178 self.update_window_edited(cx);
2179 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2180 if entry.get().entity_id() == pane.entity_id() {
2181 entry.remove();
2182 }
2183 }
2184 }
2185 pane::Event::Focus => {
2186 self.handle_pane_focused(pane.clone(), cx);
2187 }
2188 pane::Event::ZoomIn => {
2189 if pane == self.active_pane {
2190 pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2191 if pane.read(cx).has_focus(cx) {
2192 self.zoomed = Some(pane.downgrade().into());
2193 self.zoomed_position = None;
2194 }
2195 cx.notify();
2196 }
2197 }
2198 pane::Event::ZoomOut => {
2199 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2200 if self.zoomed_position.is_none() {
2201 self.zoomed = None;
2202 }
2203 cx.notify();
2204 }
2205 }
2206
2207 self.serialize_workspace(cx);
2208 }
2209
2210 pub fn split_pane(
2211 &mut self,
2212 pane_to_split: View<Pane>,
2213 split_direction: SplitDirection,
2214 cx: &mut ViewContext<Self>,
2215 ) -> View<Pane> {
2216 let new_pane = self.add_pane(cx);
2217 self.center
2218 .split(&pane_to_split, &new_pane, split_direction)
2219 .unwrap();
2220 cx.notify();
2221 new_pane
2222 }
2223
2224 pub fn split_and_clone(
2225 &mut self,
2226 pane: View<Pane>,
2227 direction: SplitDirection,
2228 cx: &mut ViewContext<Self>,
2229 ) -> Option<View<Pane>> {
2230 let item = pane.read(cx).active_item()?;
2231 let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2232 let new_pane = self.add_pane(cx);
2233 new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2234 self.center.split(&pane, &new_pane, direction).unwrap();
2235 Some(new_pane)
2236 } else {
2237 None
2238 };
2239 cx.notify();
2240 maybe_pane_handle
2241 }
2242
2243 pub fn split_pane_with_item(
2244 &mut self,
2245 pane_to_split: WeakView<Pane>,
2246 split_direction: SplitDirection,
2247 from: WeakView<Pane>,
2248 item_id_to_move: EntityId,
2249 cx: &mut ViewContext<Self>,
2250 ) {
2251 let Some(pane_to_split) = pane_to_split.upgrade() else {
2252 return;
2253 };
2254 let Some(from) = from.upgrade() else {
2255 return;
2256 };
2257
2258 let new_pane = self.add_pane(cx);
2259 self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2260 self.center
2261 .split(&pane_to_split, &new_pane, split_direction)
2262 .unwrap();
2263 cx.notify();
2264 }
2265
2266 pub fn split_pane_with_project_entry(
2267 &mut self,
2268 pane_to_split: WeakView<Pane>,
2269 split_direction: SplitDirection,
2270 project_entry: ProjectEntryId,
2271 cx: &mut ViewContext<Self>,
2272 ) -> Option<Task<Result<()>>> {
2273 let pane_to_split = pane_to_split.upgrade()?;
2274 let new_pane = self.add_pane(cx);
2275 self.center
2276 .split(&pane_to_split, &new_pane, split_direction)
2277 .unwrap();
2278
2279 let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2280 let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2281 Some(cx.foreground_executor().spawn(async move {
2282 task.await?;
2283 Ok(())
2284 }))
2285 }
2286
2287 pub fn move_item(
2288 &mut self,
2289 source: View<Pane>,
2290 destination: View<Pane>,
2291 item_id_to_move: EntityId,
2292 destination_index: usize,
2293 cx: &mut ViewContext<Self>,
2294 ) {
2295 let item_to_move = source
2296 .read(cx)
2297 .items()
2298 .enumerate()
2299 .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move);
2300
2301 if item_to_move.is_none() {
2302 log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2303 return;
2304 }
2305 let (item_ix, item_handle) = item_to_move.unwrap();
2306 let item_handle = item_handle.clone();
2307
2308 if source != destination {
2309 // Close item from previous pane
2310 source.update(cx, |source, cx| {
2311 source.remove_item(item_ix, false, cx);
2312 });
2313 }
2314
2315 // This automatically removes duplicate items in the pane
2316 destination.update(cx, |destination, cx| {
2317 destination.add_item(item_handle, true, true, Some(destination_index), cx);
2318 destination.focus(cx)
2319 });
2320 }
2321
2322 fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2323 if self.center.remove(&pane).unwrap() {
2324 self.force_remove_pane(&pane, cx);
2325 self.unfollow(&pane, cx);
2326 self.last_leaders_by_pane.remove(&pane.downgrade());
2327 for removed_item in pane.read(cx).items() {
2328 self.panes_by_item.remove(&removed_item.item_id());
2329 }
2330
2331 cx.notify();
2332 } else {
2333 self.active_item_path_changed(cx);
2334 }
2335 }
2336
2337 pub fn panes(&self) -> &[View<Pane>] {
2338 &self.panes
2339 }
2340
2341 pub fn active_pane(&self) -> &View<Pane> {
2342 &self.active_pane
2343 }
2344
2345 pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2346 let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2347 weak_pane.upgrade()
2348 }
2349
2350 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2351 self.follower_states.retain(|_, state| {
2352 if state.leader_id == peer_id {
2353 for item in state.items_by_leader_view_id.values() {
2354 item.set_leader_peer_id(None, cx);
2355 }
2356 false
2357 } else {
2358 true
2359 }
2360 });
2361 cx.notify();
2362 }
2363
2364 // fn start_following(
2365 // &mut self,
2366 // leader_id: PeerId,
2367 // cx: &mut ViewContext<Self>,
2368 // ) -> Option<Task<Result<()>>> {
2369 // let pane = self.active_pane().clone();
2370
2371 // self.last_leaders_by_pane
2372 // .insert(pane.downgrade(), leader_id);
2373 // self.unfollow(&pane, cx);
2374 // self.follower_states.insert(
2375 // pane.clone(),
2376 // FollowerState {
2377 // leader_id,
2378 // active_view_id: None,
2379 // items_by_leader_view_id: Default::default(),
2380 // },
2381 // );
2382 // cx.notify();
2383
2384 // let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2385 // let project_id = self.project.read(cx).remote_id();
2386 // let request = self.app_state.client.request(proto::Follow {
2387 // room_id,
2388 // project_id,
2389 // leader_id: Some(leader_id),
2390 // });
2391
2392 // Some(cx.spawn(|this, mut cx| async move {
2393 // let response = request.await?;
2394 // this.update(&mut cx, |this, _| {
2395 // let state = this
2396 // .follower_states
2397 // .get_mut(&pane)
2398 // .ok_or_else(|| anyhow!("following interrupted"))?;
2399 // state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2400 // Some(ViewId::from_proto(active_view_id)?)
2401 // } else {
2402 // None
2403 // };
2404 // Ok::<_, anyhow::Error>(())
2405 // })??;
2406 // Self::add_views_from_leader(
2407 // this.clone(),
2408 // leader_id,
2409 // vec![pane],
2410 // response.views,
2411 // &mut cx,
2412 // )
2413 // .await?;
2414 // this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2415 // Ok(())
2416 // }))
2417 // }
2418
2419 // pub fn follow_next_collaborator(
2420 // &mut self,
2421 // _: &FollowNextCollaborator,
2422 // cx: &mut ViewContext<Self>,
2423 // ) -> Option<Task<Result<()>>> {
2424 // let collaborators = self.project.read(cx).collaborators();
2425 // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2426 // let mut collaborators = collaborators.keys().copied();
2427 // for peer_id in collaborators.by_ref() {
2428 // if peer_id == leader_id {
2429 // break;
2430 // }
2431 // }
2432 // collaborators.next()
2433 // } else if let Some(last_leader_id) =
2434 // self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2435 // {
2436 // if collaborators.contains_key(last_leader_id) {
2437 // Some(*last_leader_id)
2438 // } else {
2439 // None
2440 // }
2441 // } else {
2442 // None
2443 // };
2444
2445 // let pane = self.active_pane.clone();
2446 // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2447 // else {
2448 // return None;
2449 // };
2450 // if Some(leader_id) == self.unfollow(&pane, cx) {
2451 // return None;
2452 // }
2453 // self.follow(leader_id, cx)
2454 // }
2455
2456 // pub fn follow(
2457 // &mut self,
2458 // leader_id: PeerId,
2459 // cx: &mut ViewContext<Self>,
2460 // ) -> Option<Task<Result<()>>> {
2461 // let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2462 // let project = self.project.read(cx);
2463
2464 // let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2465 // return None;
2466 // };
2467
2468 // let other_project_id = match remote_participant.location {
2469 // call::ParticipantLocation::External => None,
2470 // call::ParticipantLocation::UnsharedProject => None,
2471 // call::ParticipantLocation::SharedProject { project_id } => {
2472 // if Some(project_id) == project.remote_id() {
2473 // None
2474 // } else {
2475 // Some(project_id)
2476 // }
2477 // }
2478 // };
2479
2480 // // if they are active in another project, follow there.
2481 // if let Some(project_id) = other_project_id {
2482 // let app_state = self.app_state.clone();
2483 // return Some(crate::join_remote_project(
2484 // project_id,
2485 // remote_participant.user.id,
2486 // app_state,
2487 // cx,
2488 // ));
2489 // }
2490
2491 // // if you're already following, find the right pane and focus it.
2492 // for (pane, state) in &self.follower_states {
2493 // if leader_id == state.leader_id {
2494 // cx.focus(pane);
2495 // return None;
2496 // }
2497 // }
2498
2499 // // Otherwise, follow.
2500 // self.start_following(leader_id, cx)
2501 // }
2502
2503 pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2504 let follower_states = &mut self.follower_states;
2505 let state = follower_states.remove(pane)?;
2506 let leader_id = state.leader_id;
2507 for (_, item) in state.items_by_leader_view_id {
2508 item.set_leader_peer_id(None, cx);
2509 }
2510
2511 if follower_states
2512 .values()
2513 .all(|state| state.leader_id != state.leader_id)
2514 {
2515 let project_id = self.project.read(cx).remote_id();
2516 let room_id = self.call_handler.room_id(cx)?;
2517 self.app_state
2518 .client
2519 .send(proto::Unfollow {
2520 room_id,
2521 project_id,
2522 leader_id: Some(leader_id),
2523 })
2524 .log_err();
2525 }
2526
2527 cx.notify();
2528 Some(leader_id)
2529 }
2530
2531 // pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2532 // self.follower_states
2533 // .values()
2534 // .any(|state| state.leader_id == peer_id)
2535 // }
2536
2537 fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2538 let active_entry = self.active_project_path(cx);
2539 self.project
2540 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2541 self.update_window_title(cx);
2542 }
2543
2544 fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2545 let project = self.project().read(cx);
2546 let mut title = String::new();
2547
2548 if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2549 let filename = path
2550 .path
2551 .file_name()
2552 .map(|s| s.to_string_lossy())
2553 .or_else(|| {
2554 Some(Cow::Borrowed(
2555 project
2556 .worktree_for_id(path.worktree_id, cx)?
2557 .read(cx)
2558 .root_name(),
2559 ))
2560 });
2561
2562 if let Some(filename) = filename {
2563 title.push_str(filename.as_ref());
2564 title.push_str(" β ");
2565 }
2566 }
2567
2568 for (i, name) in project.worktree_root_names(cx).enumerate() {
2569 if i > 0 {
2570 title.push_str(", ");
2571 }
2572 title.push_str(name);
2573 }
2574
2575 if title.is_empty() {
2576 title = "empty project".to_string();
2577 }
2578
2579 if project.is_remote() {
2580 title.push_str(" β");
2581 } else if project.is_shared() {
2582 title.push_str(" β");
2583 }
2584
2585 // todo!()
2586 // cx.set_window_title(&title);
2587 }
2588
2589 fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2590 let is_edited = !self.project.read(cx).is_read_only()
2591 && self
2592 .items(cx)
2593 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2594 if is_edited != self.window_edited {
2595 self.window_edited = is_edited;
2596 // todo!()
2597 // cx.set_window_edited(self.window_edited)
2598 }
2599 }
2600
2601 // fn render_disconnected_overlay(
2602 // &self,
2603 // cx: &mut ViewContext<Workspace>,
2604 // ) -> Option<AnyElement<Workspace>> {
2605 // if self.project.read(cx).is_read_only() {
2606 // enum DisconnectedOverlay {}
2607 // Some(
2608 // MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2609 // let theme = &theme::current(cx);
2610 // Label::new(
2611 // "Your connection to the remote project has been lost.",
2612 // theme.workspace.disconnected_overlay.text.clone(),
2613 // )
2614 // .aligned()
2615 // .contained()
2616 // .with_style(theme.workspace.disconnected_overlay.container)
2617 // })
2618 // .with_cursor_style(CursorStyle::Arrow)
2619 // .capture_all()
2620 // .into_any_named("disconnected overlay"),
2621 // )
2622 // } else {
2623 // None
2624 // }
2625 // }
2626
2627 fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
2628 if self.notifications.is_empty() {
2629 None
2630 } else {
2631 Some(
2632 div()
2633 .absolute()
2634 .z_index(100)
2635 .right_3()
2636 .bottom_3()
2637 .w_96()
2638 .h_full()
2639 .flex()
2640 .flex_col()
2641 .justify_end()
2642 .gap_2()
2643 .children(
2644 self.notifications
2645 .iter()
2646 .map(|(_, _, notification)| notification.to_any()),
2647 ),
2648 )
2649 }
2650 }
2651
2652 // // RPC handlers
2653
2654 fn handle_follow(
2655 &mut self,
2656 _follower_project_id: Option<u64>,
2657 _cx: &mut ViewContext<Self>,
2658 ) -> proto::FollowResponse {
2659 todo!()
2660
2661 // let client = &self.app_state.client;
2662 // let project_id = self.project.read(cx).remote_id();
2663
2664 // let active_view_id = self.active_item(cx).and_then(|i| {
2665 // Some(
2666 // i.to_followable_item_handle(cx)?
2667 // .remote_id(client, cx)?
2668 // .to_proto(),
2669 // )
2670 // });
2671
2672 // cx.notify();
2673
2674 // self.last_active_view_id = active_view_id.clone();
2675 // proto::FollowResponse {
2676 // active_view_id,
2677 // views: self
2678 // .panes()
2679 // .iter()
2680 // .flat_map(|pane| {
2681 // let leader_id = self.leader_for_pane(pane);
2682 // pane.read(cx).items().filter_map({
2683 // let cx = &cx;
2684 // move |item| {
2685 // let item = item.to_followable_item_handle(cx)?;
2686 // if (project_id.is_none() || project_id != follower_project_id)
2687 // && item.is_project_item(cx)
2688 // {
2689 // return None;
2690 // }
2691 // let id = item.remote_id(client, cx)?.to_proto();
2692 // let variant = item.to_state_proto(cx)?;
2693 // Some(proto::View {
2694 // id: Some(id),
2695 // leader_id,
2696 // variant: Some(variant),
2697 // })
2698 // }
2699 // })
2700 // })
2701 // .collect(),
2702 // }
2703 }
2704
2705 fn handle_update_followers(
2706 &mut self,
2707 leader_id: PeerId,
2708 message: proto::UpdateFollowers,
2709 _cx: &mut ViewContext<Self>,
2710 ) {
2711 self.leader_updates_tx
2712 .unbounded_send((leader_id, message))
2713 .ok();
2714 }
2715
2716 async fn process_leader_update(
2717 this: &WeakView<Self>,
2718 leader_id: PeerId,
2719 update: proto::UpdateFollowers,
2720 cx: &mut AsyncWindowContext,
2721 ) -> Result<()> {
2722 match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2723 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2724 this.update(cx, |this, _| {
2725 for (_, state) in &mut this.follower_states {
2726 if state.leader_id == leader_id {
2727 state.active_view_id =
2728 if let Some(active_view_id) = update_active_view.id.clone() {
2729 Some(ViewId::from_proto(active_view_id)?)
2730 } else {
2731 None
2732 };
2733 }
2734 }
2735 anyhow::Ok(())
2736 })??;
2737 }
2738 proto::update_followers::Variant::UpdateView(update_view) => {
2739 let variant = update_view
2740 .variant
2741 .ok_or_else(|| anyhow!("missing update view variant"))?;
2742 let id = update_view
2743 .id
2744 .ok_or_else(|| anyhow!("missing update view id"))?;
2745 let mut tasks = Vec::new();
2746 this.update(cx, |this, cx| {
2747 let project = this.project.clone();
2748 for (_, state) in &mut this.follower_states {
2749 if state.leader_id == leader_id {
2750 let view_id = ViewId::from_proto(id.clone())?;
2751 if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2752 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2753 }
2754 }
2755 }
2756 anyhow::Ok(())
2757 })??;
2758 try_join_all(tasks).await.log_err();
2759 }
2760 proto::update_followers::Variant::CreateView(view) => {
2761 let panes = this.update(cx, |this, _| {
2762 this.follower_states
2763 .iter()
2764 .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2765 .cloned()
2766 .collect()
2767 })?;
2768 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2769 }
2770 }
2771 this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2772 Ok(())
2773 }
2774
2775 async fn add_views_from_leader(
2776 this: WeakView<Self>,
2777 leader_id: PeerId,
2778 panes: Vec<View<Pane>>,
2779 views: Vec<proto::View>,
2780 cx: &mut AsyncWindowContext,
2781 ) -> Result<()> {
2782 let this = this.upgrade().context("workspace dropped")?;
2783
2784 let item_builders = cx.update(|_, cx| {
2785 cx.default_global::<FollowableItemBuilders>()
2786 .values()
2787 .map(|b| b.0)
2788 .collect::<Vec<_>>()
2789 })?;
2790
2791 let mut item_tasks_by_pane = HashMap::default();
2792 for pane in panes {
2793 let mut item_tasks = Vec::new();
2794 let mut leader_view_ids = Vec::new();
2795 for view in &views {
2796 let Some(id) = &view.id else { continue };
2797 let id = ViewId::from_proto(id.clone())?;
2798 let mut variant = view.variant.clone();
2799 if variant.is_none() {
2800 Err(anyhow!("missing view variant"))?;
2801 }
2802 for build_item in &item_builders {
2803 let task = cx.update(|_, cx| {
2804 build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2805 })?;
2806 if let Some(task) = task {
2807 item_tasks.push(task);
2808 leader_view_ids.push(id);
2809 break;
2810 } else {
2811 assert!(variant.is_some());
2812 }
2813 }
2814 }
2815
2816 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2817 }
2818
2819 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2820 let items = futures::future::try_join_all(item_tasks).await?;
2821 this.update(cx, |this, cx| {
2822 let state = this.follower_states.get_mut(&pane)?;
2823 for (id, item) in leader_view_ids.into_iter().zip(items) {
2824 item.set_leader_peer_id(Some(leader_id), cx);
2825 state.items_by_leader_view_id.insert(id, item);
2826 }
2827
2828 Some(())
2829 })?;
2830 }
2831 Ok(())
2832 }
2833
2834 fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2835 let mut is_project_item = true;
2836 let mut update = proto::UpdateActiveView::default();
2837 if self.active_pane.read(cx).has_focus(cx) {
2838 let item = self
2839 .active_item(cx)
2840 .and_then(|item| item.to_followable_item_handle(cx));
2841 if let Some(item) = item {
2842 is_project_item = item.is_project_item(cx);
2843 update = proto::UpdateActiveView {
2844 id: item
2845 .remote_id(&self.app_state.client, cx)
2846 .map(|id| id.to_proto()),
2847 leader_id: self.leader_for_pane(&self.active_pane),
2848 };
2849 }
2850 }
2851
2852 if update.id != self.last_active_view_id {
2853 self.last_active_view_id = update.id.clone();
2854 self.update_followers(
2855 is_project_item,
2856 proto::update_followers::Variant::UpdateActiveView(update),
2857 cx,
2858 );
2859 }
2860 }
2861
2862 fn update_followers(
2863 &self,
2864 project_only: bool,
2865 update: proto::update_followers::Variant,
2866 cx: &mut WindowContext,
2867 ) -> Option<()> {
2868 let project_id = if project_only {
2869 self.project.read(cx).remote_id()
2870 } else {
2871 None
2872 };
2873 let room_id = self.call_handler.room_id(cx)?;
2874 self.app_state().workspace_store.update(cx, |store, cx| {
2875 store.update_followers(project_id, room_id, update, cx)
2876 })
2877 }
2878
2879 pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2880 self.follower_states.get(pane).map(|state| state.leader_id)
2881 }
2882
2883 pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2884 cx.notify();
2885
2886 let (leader_in_this_project, leader_in_this_app) =
2887 self.call_handler.peer_state(leader_id, cx)?;
2888 let mut items_to_activate = Vec::new();
2889 for (pane, state) in &self.follower_states {
2890 if state.leader_id != leader_id {
2891 continue;
2892 }
2893 if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2894 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2895 if leader_in_this_project || !item.is_project_item(cx) {
2896 items_to_activate.push((pane.clone(), item.boxed_clone()));
2897 }
2898 } else {
2899 log::warn!(
2900 "unknown view id {:?} for leader {:?}",
2901 active_view_id,
2902 leader_id
2903 );
2904 }
2905 continue;
2906 }
2907
2908 if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2909 items_to_activate.push((pane.clone(), shared_screen));
2910 }
2911 }
2912
2913 for (pane, item) in items_to_activate {
2914 let pane_was_focused = pane.read(cx).has_focus(cx);
2915 if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2916 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2917 } else {
2918 pane.update(cx, |pane, mut cx| {
2919 pane.add_item(item.boxed_clone(), false, false, None, &mut cx)
2920 });
2921 }
2922
2923 if pane_was_focused {
2924 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2925 }
2926 }
2927
2928 None
2929 }
2930
2931 fn shared_screen_for_peer(
2932 &self,
2933 peer_id: PeerId,
2934 pane: &View<Pane>,
2935 cx: &mut ViewContext<Self>,
2936 ) -> Option<Box<dyn ItemHandle>> {
2937 self.call_handler.shared_screen_for_peer(peer_id, pane, cx)
2938 // let call = self.active_call()?;
2939 // let room = call.read(cx).room()?.read(cx);
2940 // let participant = room.remote_participant_for_peer_id(peer_id)?;
2941 // let track = participant.video_tracks.values().next()?.clone();
2942 // let user = participant.user.clone();
2943
2944 // for item in pane.read(cx).items_of_type::<SharedScreen>() {
2945 // if item.read(cx).peer_id == peer_id {
2946 // return Some(item);
2947 // }
2948 // }
2949
2950 // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2951 }
2952
2953 pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2954 if cx.is_window_active() {
2955 self.update_active_view_for_followers(cx);
2956 cx.background_executor()
2957 .spawn(persistence::DB.update_timestamp(self.database_id()))
2958 .detach();
2959 } else {
2960 for pane in &self.panes {
2961 pane.update(cx, |pane, cx| {
2962 if let Some(item) = pane.active_item() {
2963 item.workspace_deactivated(cx);
2964 }
2965 if matches!(
2966 WorkspaceSettings::get_global(cx).autosave,
2967 AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2968 ) {
2969 for item in pane.items() {
2970 Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2971 .detach_and_log_err(cx);
2972 }
2973 }
2974 });
2975 }
2976 }
2977 }
2978
2979 pub fn database_id(&self) -> WorkspaceId {
2980 self.database_id
2981 }
2982
2983 fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2984 let project = self.project().read(cx);
2985
2986 if project.is_local() {
2987 Some(
2988 project
2989 .visible_worktrees(cx)
2990 .map(|worktree| worktree.read(cx).abs_path())
2991 .collect::<Vec<_>>()
2992 .into(),
2993 )
2994 } else {
2995 None
2996 }
2997 }
2998
2999 fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3000 match member {
3001 Member::Axis(PaneAxis { members, .. }) => {
3002 for child in members.iter() {
3003 self.remove_panes(child.clone(), cx)
3004 }
3005 }
3006 Member::Pane(pane) => {
3007 self.force_remove_pane(&pane, cx);
3008 }
3009 }
3010 }
3011
3012 fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3013 self.panes.retain(|p| p != pane);
3014 self.panes
3015 .last()
3016 .unwrap()
3017 .update(cx, |pane, cx| pane.focus(cx));
3018 if self.last_active_center_pane == Some(pane.downgrade()) {
3019 self.last_active_center_pane = None;
3020 }
3021 cx.notify();
3022 }
3023
3024 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3025 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3026 cx.background_executor()
3027 .timer(Duration::from_millis(100))
3028 .await;
3029 this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3030 .log_err();
3031 }));
3032 }
3033
3034 fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3035 fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3036 let (items, active) = {
3037 let pane = pane_handle.read(cx);
3038 let active_item_id = pane.active_item().map(|item| item.item_id());
3039 (
3040 pane.items()
3041 .filter_map(|item_handle| {
3042 Some(SerializedItem {
3043 kind: Arc::from(item_handle.serialized_item_kind()?),
3044 item_id: item_handle.item_id().as_u64(),
3045 active: Some(item_handle.item_id()) == active_item_id,
3046 })
3047 })
3048 .collect::<Vec<_>>(),
3049 pane.has_focus(cx),
3050 )
3051 };
3052
3053 SerializedPane::new(items, active)
3054 }
3055
3056 fn build_serialized_pane_group(
3057 pane_group: &Member,
3058 cx: &WindowContext,
3059 ) -> SerializedPaneGroup {
3060 match pane_group {
3061 Member::Axis(PaneAxis {
3062 axis,
3063 members,
3064 flexes,
3065 bounding_boxes: _,
3066 }) => SerializedPaneGroup::Group {
3067 axis: *axis,
3068 children: members
3069 .iter()
3070 .map(|member| build_serialized_pane_group(member, cx))
3071 .collect::<Vec<_>>(),
3072 flexes: Some(flexes.lock().clone()),
3073 },
3074 Member::Pane(pane_handle) => {
3075 SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3076 }
3077 }
3078 }
3079
3080 fn build_serialized_docks(
3081 this: &Workspace,
3082 cx: &mut ViewContext<Workspace>,
3083 ) -> DockStructure {
3084 let left_dock = this.left_dock.read(cx);
3085 let left_visible = left_dock.is_open();
3086 let left_active_panel = left_dock
3087 .visible_panel()
3088 .and_then(|panel| Some(panel.persistent_name().to_string()));
3089 let left_dock_zoom = left_dock
3090 .visible_panel()
3091 .map(|panel| panel.is_zoomed(cx))
3092 .unwrap_or(false);
3093
3094 let right_dock = this.right_dock.read(cx);
3095 let right_visible = right_dock.is_open();
3096 let right_active_panel = right_dock
3097 .visible_panel()
3098 .and_then(|panel| Some(panel.persistent_name().to_string()));
3099 let right_dock_zoom = right_dock
3100 .visible_panel()
3101 .map(|panel| panel.is_zoomed(cx))
3102 .unwrap_or(false);
3103
3104 let bottom_dock = this.bottom_dock.read(cx);
3105 let bottom_visible = bottom_dock.is_open();
3106 let bottom_active_panel = bottom_dock
3107 .visible_panel()
3108 .and_then(|panel| Some(panel.persistent_name().to_string()));
3109 let bottom_dock_zoom = bottom_dock
3110 .visible_panel()
3111 .map(|panel| panel.is_zoomed(cx))
3112 .unwrap_or(false);
3113
3114 DockStructure {
3115 left: DockData {
3116 visible: left_visible,
3117 active_panel: left_active_panel,
3118 zoom: left_dock_zoom,
3119 },
3120 right: DockData {
3121 visible: right_visible,
3122 active_panel: right_active_panel,
3123 zoom: right_dock_zoom,
3124 },
3125 bottom: DockData {
3126 visible: bottom_visible,
3127 active_panel: bottom_active_panel,
3128 zoom: bottom_dock_zoom,
3129 },
3130 }
3131 }
3132
3133 if let Some(location) = self.location(cx) {
3134 // Load bearing special case:
3135 // - with_local_workspace() relies on this to not have other stuff open
3136 // when you open your log
3137 if !location.paths().is_empty() {
3138 let center_group = build_serialized_pane_group(&self.center.root, cx);
3139 let docks = build_serialized_docks(self, cx);
3140
3141 let serialized_workspace = SerializedWorkspace {
3142 id: self.database_id,
3143 location,
3144 center_group,
3145 bounds: Default::default(),
3146 display: Default::default(),
3147 docks,
3148 };
3149
3150 cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3151 .detach();
3152 }
3153 }
3154 }
3155
3156 pub(crate) fn load_workspace(
3157 serialized_workspace: SerializedWorkspace,
3158 paths_to_open: Vec<Option<ProjectPath>>,
3159 cx: &mut ViewContext<Workspace>,
3160 ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3161 cx.spawn(|workspace, mut cx| async move {
3162 let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3163 (
3164 workspace.project().clone(),
3165 workspace.last_active_center_pane.clone(),
3166 )
3167 })?;
3168
3169 let mut center_group = None;
3170 let mut center_items = None;
3171
3172 // Traverse the splits tree and add to things
3173 if let Some((group, active_pane, items)) = serialized_workspace
3174 .center_group
3175 .deserialize(
3176 &project,
3177 serialized_workspace.id,
3178 workspace.clone(),
3179 &mut cx,
3180 )
3181 .await
3182 {
3183 center_items = Some(items);
3184 center_group = Some((group, active_pane))
3185 }
3186
3187 let mut items_by_project_path = cx.update(|_, cx| {
3188 center_items
3189 .unwrap_or_default()
3190 .into_iter()
3191 .filter_map(|item| {
3192 let item = item?;
3193 let project_path = item.project_path(cx)?;
3194 Some((project_path, item))
3195 })
3196 .collect::<HashMap<_, _>>()
3197 })?;
3198
3199 let opened_items = paths_to_open
3200 .into_iter()
3201 .map(|path_to_open| {
3202 path_to_open
3203 .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3204 })
3205 .collect::<Vec<_>>();
3206
3207 // Remove old panes from workspace panes list
3208 workspace.update(&mut cx, |workspace, cx| {
3209 if let Some((center_group, active_pane)) = center_group {
3210 workspace.remove_panes(workspace.center.root.clone(), cx);
3211
3212 // Swap workspace center group
3213 workspace.center = PaneGroup::with_root(center_group);
3214 workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3215 if let Some(active_pane) = active_pane {
3216 workspace.active_pane = active_pane;
3217 cx.focus_self();
3218 } else {
3219 workspace.active_pane = workspace.center.first_pane().clone();
3220 }
3221 }
3222
3223 let docks = serialized_workspace.docks;
3224 workspace.left_dock.update(cx, |dock, cx| {
3225 dock.set_open(docks.left.visible, cx);
3226 if let Some(active_panel) = docks.left.active_panel {
3227 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3228 dock.activate_panel(ix, cx);
3229 }
3230 }
3231 dock.active_panel()
3232 .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3233 if docks.left.visible && docks.left.zoom {
3234 cx.focus_self()
3235 }
3236 });
3237 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3238 workspace.right_dock.update(cx, |dock, cx| {
3239 dock.set_open(docks.right.visible, cx);
3240 if let Some(active_panel) = docks.right.active_panel {
3241 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3242 dock.activate_panel(ix, cx);
3243 }
3244 }
3245 dock.active_panel()
3246 .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3247
3248 if docks.right.visible && docks.right.zoom {
3249 cx.focus_self()
3250 }
3251 });
3252 workspace.bottom_dock.update(cx, |dock, cx| {
3253 dock.set_open(docks.bottom.visible, cx);
3254 if let Some(active_panel) = docks.bottom.active_panel {
3255 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3256 dock.activate_panel(ix, cx);
3257 }
3258 }
3259
3260 dock.active_panel()
3261 .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3262
3263 if docks.bottom.visible && docks.bottom.zoom {
3264 cx.focus_self()
3265 }
3266 });
3267
3268 cx.notify();
3269 })?;
3270
3271 // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3272 workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3273
3274 Ok(opened_items)
3275 })
3276 }
3277
3278 fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3279 self.add_workspace_actions_listeners(div, cx)
3280 // cx.add_async_action(Workspace::open);
3281 // cx.add_async_action(Workspace::follow_next_collaborator);
3282 // cx.add_async_action(Workspace::close);
3283 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3284 .on_action(cx.listener(Self::close_all_items_and_panes))
3285 // cx.add_global_action(Workspace::close_global);
3286 // cx.add_global_action(restart);
3287 .on_action(cx.listener(Self::save_all))
3288 .on_action(cx.listener(Self::add_folder_to_project))
3289 .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3290 let pane = workspace.active_pane().clone();
3291 workspace.unfollow(&pane, cx);
3292 }))
3293 .on_action(cx.listener(|workspace, action: &Save, cx| {
3294 workspace
3295 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3296 .detach_and_log_err(cx);
3297 }))
3298 .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3299 workspace
3300 .save_active_item(SaveIntent::SaveAs, cx)
3301 .detach_and_log_err(cx);
3302 }))
3303 .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3304 workspace.activate_previous_pane(cx)
3305 }))
3306 .on_action(
3307 cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3308 )
3309 .on_action(
3310 cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3311 workspace.activate_pane_in_direction(action.0, cx)
3312 }),
3313 )
3314 .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3315 workspace.swap_pane_in_direction(action.0, cx)
3316 }))
3317 .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3318 this.toggle_dock(DockPosition::Left, cx);
3319 }))
3320 .on_action(
3321 cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3322 workspace.toggle_dock(DockPosition::Right, cx);
3323 }),
3324 )
3325 .on_action(
3326 cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3327 workspace.toggle_dock(DockPosition::Bottom, cx);
3328 }),
3329 )
3330 .on_action(
3331 cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3332 workspace.close_all_docks(cx);
3333 }),
3334 )
3335 // cx.add_action(Workspace::activate_pane_at_index);
3336 // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3337 // workspace.reopen_closed_item(cx).detach();
3338 // });
3339 // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3340 // workspace
3341 // .go_back(workspace.active_pane().downgrade(), cx)
3342 // .detach();
3343 // });
3344 // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3345 // workspace
3346 // .go_forward(workspace.active_pane().downgrade(), cx)
3347 // .detach();
3348 // });
3349
3350 // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3351 // cx.spawn(|workspace, mut cx| async move {
3352 // let err = install_cli::install_cli(&cx)
3353 // .await
3354 // .context("Failed to create CLI symlink");
3355
3356 // workspace.update(&mut cx, |workspace, cx| {
3357 // if matches!(err, Err(_)) {
3358 // err.notify_err(workspace, cx);
3359 // } else {
3360 // workspace.show_notification(1, cx, |cx| {
3361 // cx.build_view(|_| {
3362 // MessageNotification::new("Successfully installed the `zed` binary")
3363 // })
3364 // });
3365 // }
3366 // })
3367 // })
3368 // .detach();
3369 // });
3370 }
3371
3372 #[cfg(any(test, feature = "test-support"))]
3373 pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3374 use node_runtime::FakeNodeRuntime;
3375
3376 let client = project.read(cx).client();
3377 let user_store = project.read(cx).user_store();
3378
3379 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3380 let app_state = Arc::new(AppState {
3381 languages: project.read(cx).languages().clone(),
3382 workspace_store,
3383 client,
3384 user_store,
3385 fs: project.read(cx).fs().clone(),
3386 build_window_options: |_, _, _| Default::default(),
3387 node_runtime: FakeNodeRuntime::new(),
3388 call_factory: |_, _| Box::new(TestCallHandler),
3389 });
3390 let workspace = Self::new(0, project, app_state, cx);
3391 workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3392 workspace
3393 }
3394
3395 // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3396 // let dock = match position {
3397 // DockPosition::Left => &self.left_dock,
3398 // DockPosition::Right => &self.right_dock,
3399 // DockPosition::Bottom => &self.bottom_dock,
3400 // };
3401 // let active_panel = dock.read(cx).visible_panel()?;
3402 // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3403 // dock.read(cx).render_placeholder(cx)
3404 // } else {
3405 // ChildView::new(dock, cx).into_any()
3406 // };
3407
3408 // Some(
3409 // element
3410 // .constrained()
3411 // .dynamically(move |constraint, _, cx| match position {
3412 // DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3413 // Vector2F::new(20., constraint.min.y()),
3414 // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3415 // ),
3416 // DockPosition::Bottom => SizeConstraint::new(
3417 // Vector2F::new(constraint.min.x(), 20.),
3418 // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3419 // ),
3420 // })
3421 // .into_any(),
3422 // )
3423 // }
3424 // }
3425 pub fn register_action<A: Action>(
3426 &mut self,
3427 callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3428 ) -> &mut Self {
3429 let callback = Arc::new(callback);
3430
3431 self.workspace_actions.push(Box::new(move |div, cx| {
3432 let callback = callback.clone();
3433 div.on_action(
3434 cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3435 )
3436 }));
3437 self
3438 }
3439
3440 fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3441 let mut div = div
3442 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3443 .on_action(cx.listener(Self::close_all_items_and_panes))
3444 .on_action(cx.listener(Self::add_folder_to_project))
3445 .on_action(cx.listener(Self::save_all))
3446 .on_action(cx.listener(Self::open));
3447 for action in self.workspace_actions.iter() {
3448 div = (action)(div, cx)
3449 }
3450 div
3451 }
3452
3453 pub fn active_modal<V: ManagedView + 'static>(
3454 &mut self,
3455 cx: &ViewContext<Self>,
3456 ) -> Option<View<V>> {
3457 self.modal_layer.read(cx).active_modal()
3458 }
3459
3460 pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3461 where
3462 B: FnOnce(&mut ViewContext<V>) -> V,
3463 {
3464 self.modal_layer
3465 .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3466 }
3467
3468 pub fn call_state(&mut self) -> &mut dyn CallHandler {
3469 &mut *self.call_handler
3470 }
3471}
3472
3473fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3474 let display_origin = cx
3475 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3476 .ok()??;
3477 ZED_WINDOW_POSITION
3478 .zip(*ZED_WINDOW_SIZE)
3479 .map(|(position, size)| {
3480 WindowBounds::Fixed(Bounds {
3481 origin: display_origin + position,
3482 size,
3483 })
3484 })
3485}
3486
3487fn open_items(
3488 serialized_workspace: Option<SerializedWorkspace>,
3489 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3490 app_state: Arc<AppState>,
3491 cx: &mut ViewContext<Workspace>,
3492) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3493 let restored_items = serialized_workspace.map(|serialized_workspace| {
3494 Workspace::load_workspace(
3495 serialized_workspace,
3496 project_paths_to_open
3497 .iter()
3498 .map(|(_, project_path)| project_path)
3499 .cloned()
3500 .collect(),
3501 cx,
3502 )
3503 });
3504
3505 cx.spawn(|workspace, mut cx| async move {
3506 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3507
3508 if let Some(restored_items) = restored_items {
3509 let restored_items = restored_items.await?;
3510
3511 let restored_project_paths = restored_items
3512 .iter()
3513 .filter_map(|item| {
3514 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3515 .ok()
3516 .flatten()
3517 })
3518 .collect::<HashSet<_>>();
3519
3520 for restored_item in restored_items {
3521 opened_items.push(restored_item.map(Ok));
3522 }
3523
3524 project_paths_to_open
3525 .iter_mut()
3526 .for_each(|(_, project_path)| {
3527 if let Some(project_path_to_open) = project_path {
3528 if restored_project_paths.contains(project_path_to_open) {
3529 *project_path = None;
3530 }
3531 }
3532 });
3533 } else {
3534 for _ in 0..project_paths_to_open.len() {
3535 opened_items.push(None);
3536 }
3537 }
3538 assert!(opened_items.len() == project_paths_to_open.len());
3539
3540 let tasks =
3541 project_paths_to_open
3542 .into_iter()
3543 .enumerate()
3544 .map(|(i, (abs_path, project_path))| {
3545 let workspace = workspace.clone();
3546 cx.spawn(|mut cx| {
3547 let fs = app_state.fs.clone();
3548 async move {
3549 let file_project_path = project_path?;
3550 if fs.is_file(&abs_path).await {
3551 Some((
3552 i,
3553 workspace
3554 .update(&mut cx, |workspace, cx| {
3555 workspace.open_path(file_project_path, None, true, cx)
3556 })
3557 .log_err()?
3558 .await,
3559 ))
3560 } else {
3561 None
3562 }
3563 }
3564 })
3565 });
3566
3567 let tasks = tasks.collect::<Vec<_>>();
3568
3569 let tasks = futures::future::join_all(tasks.into_iter());
3570 for maybe_opened_path in tasks.await.into_iter() {
3571 if let Some((i, path_open_result)) = maybe_opened_path {
3572 opened_items[i] = Some(path_open_result);
3573 }
3574 }
3575
3576 Ok(opened_items)
3577 })
3578}
3579
3580// todo!()
3581// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3582// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3583// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3584// const MESSAGE_ID: usize = 2;
3585
3586// if workspace
3587// .read_with(cx, |workspace, cx| {
3588// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3589// })
3590// .unwrap_or(false)
3591// {
3592// return;
3593// }
3594
3595// if db::kvp::KEY_VALUE_STORE
3596// .read_kvp(NEW_DOCK_HINT_KEY)
3597// .ok()
3598// .flatten()
3599// .is_some()
3600// {
3601// if !workspace
3602// .read_with(cx, |workspace, cx| {
3603// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3604// })
3605// .unwrap_or(false)
3606// {
3607// cx.update(|cx| {
3608// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3609// let entry = tracker
3610// .entry(TypeId::of::<MessageNotification>())
3611// .or_default();
3612// if !entry.contains(&MESSAGE_ID) {
3613// entry.push(MESSAGE_ID);
3614// }
3615// });
3616// });
3617// }
3618
3619// return;
3620// }
3621
3622// cx.spawn(|_| async move {
3623// db::kvp::KEY_VALUE_STORE
3624// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3625// .await
3626// .ok();
3627// })
3628// .detach();
3629
3630// workspace
3631// .update(cx, |workspace, cx| {
3632// workspace.show_notification_once(2, cx, |cx| {
3633// cx.build_view(|_| {
3634// MessageNotification::new_element(|text, _| {
3635// Text::new(
3636// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3637// text,
3638// )
3639// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3640// let code_span_background_color = settings::get::<ThemeSettings>(cx)
3641// .theme
3642// .editor
3643// .document_highlight_read_background;
3644
3645// cx.scene().push_quad(gpui::Quad {
3646// bounds,
3647// background: Some(code_span_background_color),
3648// border: Default::default(),
3649// corner_radii: (2.0).into(),
3650// })
3651// })
3652// .into_any()
3653// })
3654// .with_click_message("Read more about the new panel system")
3655// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3656// })
3657// })
3658// })
3659// .ok();
3660
3661fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3662 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3663
3664 workspace
3665 .update(cx, |workspace, cx| {
3666 if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3667 workspace.show_notification_once(0, cx, |cx| {
3668 cx.build_view(|_| {
3669 MessageNotification::new("Failed to load the database file.")
3670 .with_click_message("Click to let us know about this error")
3671 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3672 })
3673 });
3674 }
3675 })
3676 .log_err();
3677}
3678
3679impl FocusableView for Workspace {
3680 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3681 self.active_pane.focus_handle(cx)
3682 }
3683}
3684
3685impl Render for Workspace {
3686 type Element = Div;
3687
3688 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3689 let mut context = KeyContext::default();
3690 context.add("Workspace");
3691
3692 let (ui_font, ui_font_size) = {
3693 let theme_settings = ThemeSettings::get_global(cx);
3694 (
3695 theme_settings.ui_font.family.clone(),
3696 theme_settings.ui_font_size.clone(),
3697 )
3698 };
3699
3700 cx.set_rem_size(ui_font_size);
3701
3702 self.actions(div(), cx)
3703 .key_context(context)
3704 .relative()
3705 .size_full()
3706 .flex()
3707 .flex_col()
3708 .font(ui_font)
3709 .gap_0()
3710 .justify_start()
3711 .items_start()
3712 .text_color(cx.theme().colors().text)
3713 .bg(cx.theme().colors().background)
3714 .children(self.titlebar_item.clone())
3715 .child(
3716 div()
3717 .id("workspace")
3718 .relative()
3719 .flex_1()
3720 .w_full()
3721 .flex()
3722 .overflow_hidden()
3723 .border_t()
3724 .border_b()
3725 .border_color(cx.theme().colors().border)
3726 .child(self.modal_layer.clone())
3727 .child(
3728 div()
3729 .flex()
3730 .flex_row()
3731 .flex_1()
3732 .h_full()
3733 // Left Dock
3734 .child(
3735 div()
3736 .flex()
3737 .flex_none()
3738 .overflow_hidden()
3739 .child(self.left_dock.clone()),
3740 )
3741 // Panes
3742 .child(
3743 div()
3744 .flex()
3745 .flex_col()
3746 .flex_1()
3747 .child(self.center.render(
3748 &self.project,
3749 &self.follower_states,
3750 &self.active_pane,
3751 self.zoomed.as_ref(),
3752 &self.app_state,
3753 cx,
3754 ))
3755 .child(self.bottom_dock.clone()),
3756 )
3757 // Right Dock
3758 .child(
3759 div()
3760 .flex()
3761 .flex_none()
3762 .overflow_hidden()
3763 .child(self.right_dock.clone()),
3764 ),
3765 )
3766 .children(self.render_notifications(cx)),
3767 )
3768 .child(self.status_bar.clone())
3769 }
3770}
3771
3772// impl View for Workspace {
3773
3774// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3775// let theme = theme::current(cx).clone();
3776// Stack::new()
3777// .with_child(
3778// Flex::column()
3779// .with_child(self.render_titlebar(&theme, cx))
3780// .with_child(
3781// Stack::new()
3782// .with_child({
3783// let project = self.project.clone();
3784// Flex::row()
3785// .with_children(self.render_dock(DockPosition::Left, cx))
3786// .with_child(
3787// Flex::column()
3788// .with_child(
3789// FlexItem::new(
3790// self.center.render(
3791// &project,
3792// &theme,
3793// &self.follower_states,
3794// self.active_call(),
3795// self.active_pane(),
3796// self.zoomed
3797// .as_ref()
3798// .and_then(|zoomed| zoomed.upgrade(cx))
3799// .as_ref(),
3800// &self.app_state,
3801// cx,
3802// ),
3803// )
3804// .flex(1., true),
3805// )
3806// .with_children(
3807// self.render_dock(DockPosition::Bottom, cx),
3808// )
3809// .flex(1., true),
3810// )
3811// .with_children(self.render_dock(DockPosition::Right, cx))
3812// })
3813// .with_child(Overlay::new(
3814// Stack::new()
3815// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3816// enum ZoomBackground {}
3817// let zoomed = zoomed.upgrade(cx)?;
3818
3819// let mut foreground_style =
3820// theme.workspace.zoomed_pane_foreground;
3821// if let Some(zoomed_dock_position) = self.zoomed_position {
3822// foreground_style =
3823// theme.workspace.zoomed_panel_foreground;
3824// let margin = foreground_style.margin.top;
3825// let border = foreground_style.border.top;
3826
3827// // Only include a margin and border on the opposite side.
3828// foreground_style.margin.top = 0.;
3829// foreground_style.margin.left = 0.;
3830// foreground_style.margin.bottom = 0.;
3831// foreground_style.margin.right = 0.;
3832// foreground_style.border.top = false;
3833// foreground_style.border.left = false;
3834// foreground_style.border.bottom = false;
3835// foreground_style.border.right = false;
3836// match zoomed_dock_position {
3837// DockPosition::Left => {
3838// foreground_style.margin.right = margin;
3839// foreground_style.border.right = border;
3840// }
3841// DockPosition::Right => {
3842// foreground_style.margin.left = margin;
3843// foreground_style.border.left = border;
3844// }
3845// DockPosition::Bottom => {
3846// foreground_style.margin.top = margin;
3847// foreground_style.border.top = border;
3848// }
3849// }
3850// }
3851
3852// Some(
3853// ChildView::new(&zoomed, cx)
3854// .contained()
3855// .with_style(foreground_style)
3856// .aligned()
3857// .contained()
3858// .with_style(theme.workspace.zoomed_background)
3859// .mouse::<ZoomBackground>(0)
3860// .capture_all()
3861// .on_down(
3862// MouseButton::Left,
3863// |_, this: &mut Self, cx| {
3864// this.zoom_out(cx);
3865// },
3866// ),
3867// )
3868// }))
3869// .with_children(self.modal.as_ref().map(|modal| {
3870// // Prevent clicks within the modal from falling
3871// // through to the rest of the workspace.
3872// enum ModalBackground {}
3873// MouseEventHandler::new::<ModalBackground, _>(
3874// 0,
3875// cx,
3876// |_, cx| ChildView::new(modal.view.as_any(), cx),
3877// )
3878// .on_click(MouseButton::Left, |_, _, _| {})
3879// .contained()
3880// .with_style(theme.workspace.modal)
3881// .aligned()
3882// .top()
3883// }))
3884// .with_children(self.render_notifications(&theme.workspace, cx)),
3885// ))
3886// .provide_resize_bounds::<WorkspaceBounds>()
3887// .flex(1.0, true),
3888// )
3889// .with_child(ChildView::new(&self.status_bar, cx))
3890// .contained()
3891// .with_background_color(theme.workspace.background),
3892// )
3893// .with_children(DragAndDrop::render(cx))
3894// .with_children(self.render_disconnected_overlay(cx))
3895// .into_any_named("workspace")
3896// }
3897
3898// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3899// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3900// }
3901// }
3902
3903impl WorkspaceStore {
3904 pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3905 Self {
3906 workspaces: Default::default(),
3907 followers: Default::default(),
3908 _subscriptions: vec![],
3909 // client.add_request_handler(cx.weak_model(), Self::handle_follow),
3910 // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3911 // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3912 // ],
3913 client,
3914 }
3915 }
3916
3917 pub fn update_followers(
3918 &self,
3919 project_id: Option<u64>,
3920 room_id: u64,
3921 update: proto::update_followers::Variant,
3922 cx: &AppContext,
3923 ) -> Option<()> {
3924 let follower_ids: Vec<_> = self
3925 .followers
3926 .iter()
3927 .filter_map(|follower| {
3928 if follower.project_id == project_id || project_id.is_none() {
3929 Some(follower.peer_id.into())
3930 } else {
3931 None
3932 }
3933 })
3934 .collect();
3935 if follower_ids.is_empty() {
3936 return None;
3937 }
3938 self.client
3939 .send(proto::UpdateFollowers {
3940 room_id,
3941 project_id,
3942 follower_ids,
3943 variant: Some(update),
3944 })
3945 .log_err()
3946 }
3947
3948 pub async fn handle_follow(
3949 this: Model<Self>,
3950 envelope: TypedEnvelope<proto::Follow>,
3951 _: Arc<Client>,
3952 mut cx: AsyncAppContext,
3953 ) -> Result<proto::FollowResponse> {
3954 this.update(&mut cx, |this, cx| {
3955 let follower = Follower {
3956 project_id: envelope.payload.project_id,
3957 peer_id: envelope.original_sender_id()?,
3958 };
3959 let mut response = proto::FollowResponse::default();
3960 let active_project = this
3961 .workspaces
3962 .iter()
3963 .next()
3964 .and_then(|workspace| {
3965 workspace
3966 .read_with(cx, |this, cx| this.call_handler.active_project(cx))
3967 .log_err()
3968 })
3969 .flatten();
3970 for workspace in &this.workspaces {
3971 workspace
3972 .update(cx, |workspace, cx| {
3973 let handler_response = workspace.handle_follow(follower.project_id, cx);
3974 if response.views.is_empty() {
3975 response.views = handler_response.views;
3976 } else {
3977 response.views.extend_from_slice(&handler_response.views);
3978 }
3979
3980 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3981 if response.active_view_id.is_none()
3982 || Some(workspace.project.downgrade()) == active_project
3983 {
3984 response.active_view_id = Some(active_view_id);
3985 }
3986 }
3987 })
3988 .ok();
3989 }
3990
3991 if let Err(ix) = this.followers.binary_search(&follower) {
3992 this.followers.insert(ix, follower);
3993 }
3994
3995 Ok(response)
3996 })?
3997 }
3998
3999 async fn handle_unfollow(
4000 model: Model<Self>,
4001 envelope: TypedEnvelope<proto::Unfollow>,
4002 _: Arc<Client>,
4003 mut cx: AsyncAppContext,
4004 ) -> Result<()> {
4005 model.update(&mut cx, |this, _| {
4006 let follower = Follower {
4007 project_id: envelope.payload.project_id,
4008 peer_id: envelope.original_sender_id()?,
4009 };
4010 if let Ok(ix) = this.followers.binary_search(&follower) {
4011 this.followers.remove(ix);
4012 }
4013 Ok(())
4014 })?
4015 }
4016
4017 async fn handle_update_followers(
4018 this: Model<Self>,
4019 envelope: TypedEnvelope<proto::UpdateFollowers>,
4020 _: Arc<Client>,
4021 mut cx: AsyncWindowContext,
4022 ) -> Result<()> {
4023 let leader_id = envelope.original_sender_id()?;
4024 let update = envelope.payload;
4025
4026 this.update(&mut cx, |this, cx| {
4027 for workspace in &this.workspaces {
4028 workspace.update(cx, |workspace, cx| {
4029 let project_id = workspace.project.read(cx).remote_id();
4030 if update.project_id != project_id && update.project_id.is_some() {
4031 return;
4032 }
4033 workspace.handle_update_followers(leader_id, update.clone(), cx);
4034 })?;
4035 }
4036 Ok(())
4037 })?
4038 }
4039}
4040
4041impl ViewId {
4042 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4043 Ok(Self {
4044 creator: message
4045 .creator
4046 .ok_or_else(|| anyhow!("creator is missing"))?,
4047 id: message.id,
4048 })
4049 }
4050
4051 pub(crate) fn to_proto(&self) -> proto::ViewId {
4052 proto::ViewId {
4053 creator: Some(self.creator),
4054 id: self.id,
4055 }
4056 }
4057}
4058
4059pub trait WorkspaceHandle {
4060 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4061}
4062
4063impl WorkspaceHandle for View<Workspace> {
4064 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4065 self.read(cx)
4066 .worktrees(cx)
4067 .flat_map(|worktree| {
4068 let worktree_id = worktree.read(cx).id();
4069 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4070 worktree_id,
4071 path: f.path.clone(),
4072 })
4073 })
4074 .collect::<Vec<_>>()
4075 }
4076}
4077
4078impl std::fmt::Debug for OpenPaths {
4079 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4080 f.debug_struct("OpenPaths")
4081 .field("paths", &self.paths)
4082 .finish()
4083 }
4084}
4085
4086pub struct WorkspaceCreated(pub WeakView<Workspace>);
4087
4088pub fn activate_workspace_for_project(
4089 cx: &mut AppContext,
4090 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4091) -> Option<WindowHandle<Workspace>> {
4092 for window in cx.windows() {
4093 let Some(workspace) = window.downcast::<Workspace>() else {
4094 continue;
4095 };
4096
4097 let predicate = workspace
4098 .update(cx, |workspace, cx| {
4099 let project = workspace.project.read(cx);
4100 if predicate(project, cx) {
4101 cx.activate_window();
4102 true
4103 } else {
4104 false
4105 }
4106 })
4107 .log_err()
4108 .unwrap_or(false);
4109
4110 if predicate {
4111 return Some(workspace);
4112 }
4113 }
4114
4115 None
4116}
4117
4118pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4119 DB.last_workspace().await.log_err().flatten()
4120}
4121
4122// async fn join_channel_internal(
4123// channel_id: u64,
4124// app_state: &Arc<AppState>,
4125// requesting_window: Option<WindowHandle<Workspace>>,
4126// active_call: &ModelHandle<ActiveCall>,
4127// cx: &mut AsyncAppContext,
4128// ) -> Result<bool> {
4129// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4130// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4131// return (false, None);
4132// };
4133
4134// let already_in_channel = room.channel_id() == Some(channel_id);
4135// let should_prompt = room.is_sharing_project()
4136// && room.remote_participants().len() > 0
4137// && !already_in_channel;
4138// let open_room = if already_in_channel {
4139// active_call.room().cloned()
4140// } else {
4141// None
4142// };
4143// (should_prompt, open_room)
4144// });
4145
4146// if let Some(room) = open_room {
4147// let task = room.update(cx, |room, cx| {
4148// if let Some((project, host)) = room.most_active_project(cx) {
4149// return Some(join_remote_project(project, host, app_state.clone(), cx));
4150// }
4151
4152// None
4153// });
4154// if let Some(task) = task {
4155// task.await?;
4156// }
4157// return anyhow::Ok(true);
4158// }
4159
4160// if should_prompt {
4161// if let Some(workspace) = requesting_window {
4162// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4163// let answer = window.prompt(
4164// PromptLevel::Warning,
4165// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4166// &["Yes, Join Channel", "Cancel"],
4167// cx,
4168// );
4169
4170// if let Some(mut answer) = answer {
4171// if answer.next().await == Some(1) {
4172// return Ok(false);
4173// }
4174// }
4175// } else {
4176// return Ok(false); // unreachable!() hopefully
4177// }
4178// } else {
4179// return Ok(false); // unreachable!() hopefully
4180// }
4181// }
4182
4183// let client = cx.read(|cx| active_call.read(cx).client());
4184
4185// let mut client_status = client.status();
4186
4187// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4188// 'outer: loop {
4189// let Some(status) = client_status.recv().await else {
4190// return Err(anyhow!("error connecting"));
4191// };
4192
4193// match status {
4194// Status::Connecting
4195// | Status::Authenticating
4196// | Status::Reconnecting
4197// | Status::Reauthenticating => continue,
4198// Status::Connected { .. } => break 'outer,
4199// Status::SignedOut => return Err(anyhow!("not signed in")),
4200// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4201// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4202// return Err(anyhow!("zed is offline"))
4203// }
4204// }
4205// }
4206
4207// let room = active_call
4208// .update(cx, |active_call, cx| {
4209// active_call.join_channel(channel_id, cx)
4210// })
4211// .await?;
4212
4213// room.update(cx, |room, _| room.room_update_completed())
4214// .await;
4215
4216// let task = room.update(cx, |room, cx| {
4217// if let Some((project, host)) = room.most_active_project(cx) {
4218// return Some(join_remote_project(project, host, app_state.clone(), cx));
4219// }
4220
4221// None
4222// });
4223// if let Some(task) = task {
4224// task.await?;
4225// return anyhow::Ok(true);
4226// }
4227// anyhow::Ok(false)
4228// }
4229
4230// pub fn join_channel(
4231// channel_id: u64,
4232// app_state: Arc<AppState>,
4233// requesting_window: Option<WindowHandle<Workspace>>,
4234// cx: &mut AppContext,
4235// ) -> Task<Result<()>> {
4236// let active_call = ActiveCall::global(cx);
4237// cx.spawn(|mut cx| async move {
4238// let result = join_channel_internal(
4239// channel_id,
4240// &app_state,
4241// requesting_window,
4242// &active_call,
4243// &mut cx,
4244// )
4245// .await;
4246
4247// // join channel succeeded, and opened a window
4248// if matches!(result, Ok(true)) {
4249// return anyhow::Ok(());
4250// }
4251
4252// if requesting_window.is_some() {
4253// return anyhow::Ok(());
4254// }
4255
4256// // find an existing workspace to focus and show call controls
4257// let mut active_window = activate_any_workspace_window(&mut cx);
4258// if active_window.is_none() {
4259// // no open workspaces, make one to show the error in (blergh)
4260// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4261// .await;
4262// }
4263
4264// active_window = activate_any_workspace_window(&mut cx);
4265// if active_window.is_none() {
4266// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4267// }
4268
4269// if let Err(err) = result {
4270// let prompt = active_window.unwrap().prompt(
4271// PromptLevel::Critical,
4272// &format!("Failed to join channel: {}", err),
4273// &["Ok"],
4274// &mut cx,
4275// );
4276// if let Some(mut prompt) = prompt {
4277// prompt.next().await;
4278// } else {
4279// return Err(err);
4280// }
4281// }
4282
4283// // return ok, we showed the error to the user.
4284// return anyhow::Ok(());
4285// })
4286// }
4287
4288// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4289// for window in cx.windows() {
4290// let found = window.update(cx, |cx| {
4291// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4292// if is_workspace {
4293// cx.activate_window();
4294// }
4295// is_workspace
4296// });
4297// if found == Some(true) {
4298// return Some(window);
4299// }
4300// }
4301// None
4302// }
4303
4304#[allow(clippy::type_complexity)]
4305pub fn open_paths(
4306 abs_paths: &[PathBuf],
4307 app_state: &Arc<AppState>,
4308 requesting_window: Option<WindowHandle<Workspace>>,
4309 cx: &mut AppContext,
4310) -> Task<
4311 anyhow::Result<(
4312 WindowHandle<Workspace>,
4313 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4314 )>,
4315> {
4316 let app_state = app_state.clone();
4317 let abs_paths = abs_paths.to_vec();
4318 // Open paths in existing workspace if possible
4319 let existing = activate_workspace_for_project(cx, {
4320 let abs_paths = abs_paths.clone();
4321 move |project, cx| project.contains_paths(&abs_paths, cx)
4322 });
4323 cx.spawn(move |mut cx| async move {
4324 if let Some(existing) = existing {
4325 // // Ok((
4326 // existing.clone(),
4327 // cx.update_window_root(&existing, |workspace, cx| {
4328 // workspace.open_paths(abs_paths, true, cx)
4329 // })?
4330 // .await,
4331 // ))
4332 todo!()
4333 } else {
4334 cx.update(move |cx| {
4335 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4336 })?
4337 .await
4338 }
4339 })
4340}
4341
4342pub fn open_new(
4343 app_state: &Arc<AppState>,
4344 cx: &mut AppContext,
4345 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4346) -> Task<()> {
4347 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4348 cx.spawn(|mut cx| async move {
4349 if let Some((workspace, opened_paths)) = task.await.log_err() {
4350 workspace
4351 .update(&mut cx, |workspace, cx| {
4352 if opened_paths.is_empty() {
4353 init(workspace, cx)
4354 }
4355 })
4356 .log_err();
4357 }
4358 })
4359}
4360
4361pub fn create_and_open_local_file(
4362 path: &'static Path,
4363 cx: &mut ViewContext<Workspace>,
4364 default_content: impl 'static + Send + FnOnce() -> Rope,
4365) -> Task<Result<Box<dyn ItemHandle>>> {
4366 cx.spawn(|workspace, mut cx| async move {
4367 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4368 if !fs.is_file(path).await {
4369 fs.create_file(path, Default::default()).await?;
4370 fs.save(path, &default_content(), Default::default())
4371 .await?;
4372 }
4373
4374 let mut items = workspace
4375 .update(&mut cx, |workspace, cx| {
4376 workspace.with_local_workspace(cx, |workspace, cx| {
4377 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4378 })
4379 })?
4380 .await?
4381 .await;
4382
4383 let item = items.pop().flatten();
4384 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4385 })
4386}
4387
4388// pub fn join_remote_project(
4389// project_id: u64,
4390// follow_user_id: u64,
4391// app_state: Arc<AppState>,
4392// cx: &mut AppContext,
4393// ) -> Task<Result<()>> {
4394// cx.spawn(|mut cx| async move {
4395// let windows = cx.windows();
4396// let existing_workspace = windows.into_iter().find_map(|window| {
4397// window.downcast::<Workspace>().and_then(|window| {
4398// window
4399// .read_root_with(&cx, |workspace, cx| {
4400// if workspace.project().read(cx).remote_id() == Some(project_id) {
4401// Some(cx.handle().downgrade())
4402// } else {
4403// None
4404// }
4405// })
4406// .unwrap_or(None)
4407// })
4408// });
4409
4410// let workspace = if let Some(existing_workspace) = existing_workspace {
4411// existing_workspace
4412// } else {
4413// let active_call = cx.read(ActiveCall::global);
4414// let room = active_call
4415// .read_with(&cx, |call, _| call.room().cloned())
4416// .ok_or_else(|| anyhow!("not in a call"))?;
4417// let project = room
4418// .update(&mut cx, |room, cx| {
4419// room.join_project(
4420// project_id,
4421// app_state.languages.clone(),
4422// app_state.fs.clone(),
4423// cx,
4424// )
4425// })
4426// .await?;
4427
4428// let window_bounds_override = window_bounds_env_override(&cx);
4429// let window = cx.add_window(
4430// (app_state.build_window_options)(
4431// window_bounds_override,
4432// None,
4433// cx.platform().as_ref(),
4434// ),
4435// |cx| Workspace::new(0, project, app_state.clone(), cx),
4436// );
4437// let workspace = window.root(&cx).unwrap();
4438// (app_state.initialize_workspace)(
4439// workspace.downgrade(),
4440// false,
4441// app_state.clone(),
4442// cx.clone(),
4443// )
4444// .await
4445// .log_err();
4446
4447// workspace.downgrade()
4448// };
4449
4450// workspace.window().activate(&mut cx);
4451// cx.platform().activate(true);
4452
4453// workspace.update(&mut cx, |workspace, cx| {
4454// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4455// let follow_peer_id = room
4456// .read(cx)
4457// .remote_participants()
4458// .iter()
4459// .find(|(_, participant)| participant.user.id == follow_user_id)
4460// .map(|(_, p)| p.peer_id)
4461// .or_else(|| {
4462// // If we couldn't follow the given user, follow the host instead.
4463// let collaborator = workspace
4464// .project()
4465// .read(cx)
4466// .collaborators()
4467// .values()
4468// .find(|collaborator| collaborator.replica_id == 0)?;
4469// Some(collaborator.peer_id)
4470// });
4471
4472// if let Some(follow_peer_id) = follow_peer_id {
4473// workspace
4474// .follow(follow_peer_id, cx)
4475// .map(|follow| follow.detach_and_log_err(cx));
4476// }
4477// }
4478// })?;
4479
4480// anyhow::Ok(())
4481// })
4482// }
4483
4484// pub fn restart(_: &Restart, cx: &mut AppContext) {
4485// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4486// cx.spawn(|mut cx| async move {
4487// let mut workspace_windows = cx
4488// .windows()
4489// .into_iter()
4490// .filter_map(|window| window.downcast::<Workspace>())
4491// .collect::<Vec<_>>();
4492
4493// // If multiple windows have unsaved changes, and need a save prompt,
4494// // prompt in the active window before switching to a different window.
4495// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4496
4497// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4498// let answer = window.prompt(
4499// PromptLevel::Info,
4500// "Are you sure you want to restart?",
4501// &["Restart", "Cancel"],
4502// &mut cx,
4503// );
4504
4505// if let Some(mut answer) = answer {
4506// let answer = answer.next().await;
4507// if answer != Some(0) {
4508// return Ok(());
4509// }
4510// }
4511// }
4512
4513// // If the user cancels any save prompt, then keep the app open.
4514// for window in workspace_windows {
4515// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4516// workspace.prepare_to_close(true, cx)
4517// }) {
4518// if !should_close.await? {
4519// return Ok(());
4520// }
4521// }
4522// }
4523// cx.platform().restart();
4524// anyhow::Ok(())
4525// })
4526// .detach_and_log_err(cx);
4527// }
4528
4529fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4530 let mut parts = value.split(',');
4531 let x: usize = parts.next()?.parse().ok()?;
4532 let y: usize = parts.next()?.parse().ok()?;
4533 Some(point((x as f64).into(), (y as f64).into()))
4534}
4535
4536fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4537 let mut parts = value.split(',');
4538 let width: usize = parts.next()?.parse().ok()?;
4539 let height: usize = parts.next()?.parse().ok()?;
4540 Some(size((width as f64).into(), (height as f64).into()))
4541}
4542
4543// #[cfg(test)]
4544// mod tests {
4545// use super::*;
4546// use crate::{
4547// dock::test::TestPanel,
4548// item::test::{TestItem, TestItemEvent, TestProjectItem},
4549// };
4550// use fs::FakeFs;
4551// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4552// use project::{Project, ProjectEntryId};
4553// use serde_json::json;
4554// use settings::SettingsStore;
4555// use std::{cell::RefCell, rc::Rc};
4556
4557// #[gpui::test]
4558// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4559// init_test(cx);
4560
4561// let fs = FakeFs::new(cx.background());
4562// let project = Project::test(fs, [], cx).await;
4563// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4564// let workspace = window.root(cx);
4565
4566// // Adding an item with no ambiguity renders the tab without detail.
4567// let item1 = window.build_view(cx, |_| {
4568// let mut item = TestItem::new();
4569// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4570// item
4571// });
4572// workspace.update(cx, |workspace, cx| {
4573// workspace.add_item(Box::new(item1.clone()), cx);
4574// });
4575// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4576
4577// // Adding an item that creates ambiguity increases the level of detail on
4578// // both tabs.
4579// let item2 = window.build_view(cx, |_| {
4580// let mut item = TestItem::new();
4581// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4582// item
4583// });
4584// workspace.update(cx, |workspace, cx| {
4585// workspace.add_item(Box::new(item2.clone()), cx);
4586// });
4587// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4588// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4589
4590// // Adding an item that creates ambiguity increases the level of detail only
4591// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4592// // we stop at the highest detail available.
4593// let item3 = window.build_view(cx, |_| {
4594// let mut item = TestItem::new();
4595// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4596// item
4597// });
4598// workspace.update(cx, |workspace, cx| {
4599// workspace.add_item(Box::new(item3.clone()), cx);
4600// });
4601// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4602// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4603// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4604// }
4605
4606// #[gpui::test]
4607// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4608// init_test(cx);
4609
4610// let fs = FakeFs::new(cx.background());
4611// fs.insert_tree(
4612// "/root1",
4613// json!({
4614// "one.txt": "",
4615// "two.txt": "",
4616// }),
4617// )
4618// .await;
4619// fs.insert_tree(
4620// "/root2",
4621// json!({
4622// "three.txt": "",
4623// }),
4624// )
4625// .await;
4626
4627// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4628// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4629// let workspace = window.root(cx);
4630// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4631// let worktree_id = project.read_with(cx, |project, cx| {
4632// project.worktrees().next().unwrap().read(cx).id()
4633// });
4634
4635// let item1 = window.build_view(cx, |cx| {
4636// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4637// });
4638// let item2 = window.build_view(cx, |cx| {
4639// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4640// });
4641
4642// // Add an item to an empty pane
4643// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4644// project.read_with(cx, |project, cx| {
4645// assert_eq!(
4646// project.active_entry(),
4647// project
4648// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4649// .map(|e| e.id)
4650// );
4651// });
4652// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4653
4654// // Add a second item to a non-empty pane
4655// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4656// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4657// project.read_with(cx, |project, cx| {
4658// assert_eq!(
4659// project.active_entry(),
4660// project
4661// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4662// .map(|e| e.id)
4663// );
4664// });
4665
4666// // Close the active item
4667// pane.update(cx, |pane, cx| {
4668// pane.close_active_item(&Default::default(), cx).unwrap()
4669// })
4670// .await
4671// .unwrap();
4672// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4673// project.read_with(cx, |project, cx| {
4674// assert_eq!(
4675// project.active_entry(),
4676// project
4677// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4678// .map(|e| e.id)
4679// );
4680// });
4681
4682// // Add a project folder
4683// project
4684// .update(cx, |project, cx| {
4685// project.find_or_create_local_worktree("/root2", true, cx)
4686// })
4687// .await
4688// .unwrap();
4689// assert_eq!(
4690// window.current_title(cx).as_deref(),
4691// Some("one.txt β root1, root2")
4692// );
4693
4694// // Remove a project folder
4695// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4696// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4697// }
4698
4699// #[gpui::test]
4700// async fn test_close_window(cx: &mut TestAppContext) {
4701// init_test(cx);
4702
4703// let fs = FakeFs::new(cx.background());
4704// fs.insert_tree("/root", json!({ "one": "" })).await;
4705
4706// let project = Project::test(fs, ["root".as_ref()], cx).await;
4707// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4708// let workspace = window.root(cx);
4709
4710// // When there are no dirty items, there's nothing to do.
4711// let item1 = window.build_view(cx, |_| TestItem::new());
4712// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4713// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4714// assert!(task.await.unwrap());
4715
4716// // When there are dirty untitled items, prompt to save each one. If the user
4717// // cancels any prompt, then abort.
4718// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4719// let item3 = window.build_view(cx, |cx| {
4720// TestItem::new()
4721// .with_dirty(true)
4722// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4723// });
4724// workspace.update(cx, |w, cx| {
4725// w.add_item(Box::new(item2.clone()), cx);
4726// w.add_item(Box::new(item3.clone()), cx);
4727// });
4728// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4729// cx.foreground().run_until_parked();
4730// window.simulate_prompt_answer(2, cx); // cancel save all
4731// cx.foreground().run_until_parked();
4732// window.simulate_prompt_answer(2, cx); // cancel save all
4733// cx.foreground().run_until_parked();
4734// assert!(!window.has_pending_prompt(cx));
4735// assert!(!task.await.unwrap());
4736// }
4737
4738// #[gpui::test]
4739// async fn test_close_pane_items(cx: &mut TestAppContext) {
4740// init_test(cx);
4741
4742// let fs = FakeFs::new(cx.background());
4743
4744// let project = Project::test(fs, None, cx).await;
4745// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4746// let workspace = window.root(cx);
4747
4748// let item1 = window.build_view(cx, |cx| {
4749// TestItem::new()
4750// .with_dirty(true)
4751// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4752// });
4753// let item2 = window.build_view(cx, |cx| {
4754// TestItem::new()
4755// .with_dirty(true)
4756// .with_conflict(true)
4757// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4758// });
4759// let item3 = window.build_view(cx, |cx| {
4760// TestItem::new()
4761// .with_dirty(true)
4762// .with_conflict(true)
4763// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4764// });
4765// let item4 = window.build_view(cx, |cx| {
4766// TestItem::new()
4767// .with_dirty(true)
4768// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4769// });
4770// let pane = workspace.update(cx, |workspace, cx| {
4771// workspace.add_item(Box::new(item1.clone()), cx);
4772// workspace.add_item(Box::new(item2.clone()), cx);
4773// workspace.add_item(Box::new(item3.clone()), cx);
4774// workspace.add_item(Box::new(item4.clone()), cx);
4775// workspace.active_pane().clone()
4776// });
4777
4778// let close_items = pane.update(cx, |pane, cx| {
4779// pane.activate_item(1, true, true, cx);
4780// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4781// let item1_id = item1.id();
4782// let item3_id = item3.id();
4783// let item4_id = item4.id();
4784// pane.close_items(cx, SaveIntent::Close, move |id| {
4785// [item1_id, item3_id, item4_id].contains(&id)
4786// })
4787// });
4788// cx.foreground().run_until_parked();
4789
4790// assert!(window.has_pending_prompt(cx));
4791// // Ignore "Save all" prompt
4792// window.simulate_prompt_answer(2, cx);
4793// cx.foreground().run_until_parked();
4794// // There's a prompt to save item 1.
4795// pane.read_with(cx, |pane, _| {
4796// assert_eq!(pane.items_len(), 4);
4797// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4798// });
4799// // Confirm saving item 1.
4800// window.simulate_prompt_answer(0, cx);
4801// cx.foreground().run_until_parked();
4802
4803// // Item 1 is saved. There's a prompt to save item 3.
4804// pane.read_with(cx, |pane, cx| {
4805// assert_eq!(item1.read(cx).save_count, 1);
4806// assert_eq!(item1.read(cx).save_as_count, 0);
4807// assert_eq!(item1.read(cx).reload_count, 0);
4808// assert_eq!(pane.items_len(), 3);
4809// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4810// });
4811// assert!(window.has_pending_prompt(cx));
4812
4813// // Cancel saving item 3.
4814// window.simulate_prompt_answer(1, cx);
4815// cx.foreground().run_until_parked();
4816
4817// // Item 3 is reloaded. There's a prompt to save item 4.
4818// pane.read_with(cx, |pane, cx| {
4819// assert_eq!(item3.read(cx).save_count, 0);
4820// assert_eq!(item3.read(cx).save_as_count, 0);
4821// assert_eq!(item3.read(cx).reload_count, 1);
4822// assert_eq!(pane.items_len(), 2);
4823// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4824// });
4825// assert!(window.has_pending_prompt(cx));
4826
4827// // Confirm saving item 4.
4828// window.simulate_prompt_answer(0, cx);
4829// cx.foreground().run_until_parked();
4830
4831// // There's a prompt for a path for item 4.
4832// cx.simulate_new_path_selection(|_| Some(Default::default()));
4833// close_items.await.unwrap();
4834
4835// // The requested items are closed.
4836// pane.read_with(cx, |pane, cx| {
4837// assert_eq!(item4.read(cx).save_count, 0);
4838// assert_eq!(item4.read(cx).save_as_count, 1);
4839// assert_eq!(item4.read(cx).reload_count, 0);
4840// assert_eq!(pane.items_len(), 1);
4841// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4842// });
4843// }
4844
4845// #[gpui::test]
4846// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4847// init_test(cx);
4848
4849// let fs = FakeFs::new(cx.background());
4850
4851// let project = Project::test(fs, [], cx).await;
4852// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4853// let workspace = window.root(cx);
4854
4855// // Create several workspace items with single project entries, and two
4856// // workspace items with multiple project entries.
4857// let single_entry_items = (0..=4)
4858// .map(|project_entry_id| {
4859// window.build_view(cx, |cx| {
4860// TestItem::new()
4861// .with_dirty(true)
4862// .with_project_items(&[TestProjectItem::new(
4863// project_entry_id,
4864// &format!("{project_entry_id}.txt"),
4865// cx,
4866// )])
4867// })
4868// })
4869// .collect::<Vec<_>>();
4870// let item_2_3 = window.build_view(cx, |cx| {
4871// TestItem::new()
4872// .with_dirty(true)
4873// .with_singleton(false)
4874// .with_project_items(&[
4875// single_entry_items[2].read(cx).project_items[0].clone(),
4876// single_entry_items[3].read(cx).project_items[0].clone(),
4877// ])
4878// });
4879// let item_3_4 = window.build_view(cx, |cx| {
4880// TestItem::new()
4881// .with_dirty(true)
4882// .with_singleton(false)
4883// .with_project_items(&[
4884// single_entry_items[3].read(cx).project_items[0].clone(),
4885// single_entry_items[4].read(cx).project_items[0].clone(),
4886// ])
4887// });
4888
4889// // Create two panes that contain the following project entries:
4890// // left pane:
4891// // multi-entry items: (2, 3)
4892// // single-entry items: 0, 1, 2, 3, 4
4893// // right pane:
4894// // single-entry items: 1
4895// // multi-entry items: (3, 4)
4896// let left_pane = workspace.update(cx, |workspace, cx| {
4897// let left_pane = workspace.active_pane().clone();
4898// workspace.add_item(Box::new(item_2_3.clone()), cx);
4899// for item in single_entry_items {
4900// workspace.add_item(Box::new(item), cx);
4901// }
4902// left_pane.update(cx, |pane, cx| {
4903// pane.activate_item(2, true, true, cx);
4904// });
4905
4906// workspace
4907// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4908// .unwrap();
4909
4910// left_pane
4911// });
4912
4913// //Need to cause an effect flush in order to respect new focus
4914// workspace.update(cx, |workspace, cx| {
4915// workspace.add_item(Box::new(item_3_4.clone()), cx);
4916// cx.focus(&left_pane);
4917// });
4918
4919// // When closing all of the items in the left pane, we should be prompted twice:
4920// // once for project entry 0, and once for project entry 2. After those two
4921// // prompts, the task should complete.
4922
4923// let close = left_pane.update(cx, |pane, cx| {
4924// pane.close_items(cx, SaveIntent::Close, move |_| true)
4925// });
4926// cx.foreground().run_until_parked();
4927// // Discard "Save all" prompt
4928// window.simulate_prompt_answer(2, cx);
4929
4930// cx.foreground().run_until_parked();
4931// left_pane.read_with(cx, |pane, cx| {
4932// assert_eq!(
4933// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4934// &[ProjectEntryId::from_proto(0)]
4935// );
4936// });
4937// window.simulate_prompt_answer(0, cx);
4938
4939// cx.foreground().run_until_parked();
4940// left_pane.read_with(cx, |pane, cx| {
4941// assert_eq!(
4942// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4943// &[ProjectEntryId::from_proto(2)]
4944// );
4945// });
4946// window.simulate_prompt_answer(0, cx);
4947
4948// cx.foreground().run_until_parked();
4949// close.await.unwrap();
4950// left_pane.read_with(cx, |pane, _| {
4951// assert_eq!(pane.items_len(), 0);
4952// });
4953// }
4954
4955// #[gpui::test]
4956// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4957// init_test(cx);
4958
4959// let fs = FakeFs::new(cx.background());
4960
4961// let project = Project::test(fs, [], cx).await;
4962// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4963// let workspace = window.root(cx);
4964// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4965
4966// let item = window.build_view(cx, |cx| {
4967// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4968// });
4969// let item_id = item.id();
4970// workspace.update(cx, |workspace, cx| {
4971// workspace.add_item(Box::new(item.clone()), cx);
4972// });
4973
4974// // Autosave on window change.
4975// item.update(cx, |item, cx| {
4976// cx.update_global(|settings: &mut SettingsStore, cx| {
4977// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4978// settings.autosave = Some(AutosaveSetting::OnWindowChange);
4979// })
4980// });
4981// item.is_dirty = true;
4982// });
4983
4984// // Deactivating the window saves the file.
4985// window.simulate_deactivation(cx);
4986// deterministic.run_until_parked();
4987// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4988
4989// // Autosave on focus change.
4990// item.update(cx, |item, cx| {
4991// cx.focus_self();
4992// cx.update_global(|settings: &mut SettingsStore, cx| {
4993// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4994// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4995// })
4996// });
4997// item.is_dirty = true;
4998// });
4999
5000// // Blurring the item saves the file.
5001// item.update(cx, |_, cx| cx.blur());
5002// deterministic.run_until_parked();
5003// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5004
5005// // Deactivating the window still saves the file.
5006// window.simulate_activation(cx);
5007// item.update(cx, |item, cx| {
5008// cx.focus_self();
5009// item.is_dirty = true;
5010// });
5011// window.simulate_deactivation(cx);
5012
5013// deterministic.run_until_parked();
5014// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5015
5016// // Autosave after delay.
5017// item.update(cx, |item, cx| {
5018// cx.update_global(|settings: &mut SettingsStore, cx| {
5019// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5020// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5021// })
5022// });
5023// item.is_dirty = true;
5024// cx.emit(TestItemEvent::Edit);
5025// });
5026
5027// // Delay hasn't fully expired, so the file is still dirty and unsaved.
5028// deterministic.advance_clock(Duration::from_millis(250));
5029// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5030
5031// // After delay expires, the file is saved.
5032// deterministic.advance_clock(Duration::from_millis(250));
5033// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5034
5035// // Autosave on focus change, ensuring closing the tab counts as such.
5036// item.update(cx, |item, cx| {
5037// cx.update_global(|settings: &mut SettingsStore, cx| {
5038// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5039// settings.autosave = Some(AutosaveSetting::OnFocusChange);
5040// })
5041// });
5042// item.is_dirty = true;
5043// });
5044
5045// pane.update(cx, |pane, cx| {
5046// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5047// })
5048// .await
5049// .unwrap();
5050// assert!(!window.has_pending_prompt(cx));
5051// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5052
5053// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5054// workspace.update(cx, |workspace, cx| {
5055// workspace.add_item(Box::new(item.clone()), cx);
5056// });
5057// item.update(cx, |item, cx| {
5058// item.project_items[0].update(cx, |item, _| {
5059// item.entry_id = None;
5060// });
5061// item.is_dirty = true;
5062// cx.blur();
5063// });
5064// deterministic.run_until_parked();
5065// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5066
5067// // Ensure autosave is prevented for deleted files also when closing the buffer.
5068// let _close_items = pane.update(cx, |pane, cx| {
5069// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5070// });
5071// deterministic.run_until_parked();
5072// assert!(window.has_pending_prompt(cx));
5073// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5074// }
5075
5076// #[gpui::test]
5077// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5078// init_test(cx);
5079
5080// let fs = FakeFs::new(cx.background());
5081
5082// let project = Project::test(fs, [], cx).await;
5083// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5084// let workspace = window.root(cx);
5085
5086// let item = window.build_view(cx, |cx| {
5087// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5088// });
5089// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5090// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5091// let toolbar_notify_count = Rc::new(RefCell::new(0));
5092
5093// workspace.update(cx, |workspace, cx| {
5094// workspace.add_item(Box::new(item.clone()), cx);
5095// let toolbar_notification_count = toolbar_notify_count.clone();
5096// cx.observe(&toolbar, move |_, _, _| {
5097// *toolbar_notification_count.borrow_mut() += 1
5098// })
5099// .detach();
5100// });
5101
5102// pane.read_with(cx, |pane, _| {
5103// assert!(!pane.can_navigate_backward());
5104// assert!(!pane.can_navigate_forward());
5105// });
5106
5107// item.update(cx, |item, cx| {
5108// item.set_state("one".to_string(), cx);
5109// });
5110
5111// // Toolbar must be notified to re-render the navigation buttons
5112// assert_eq!(*toolbar_notify_count.borrow(), 1);
5113
5114// pane.read_with(cx, |pane, _| {
5115// assert!(pane.can_navigate_backward());
5116// assert!(!pane.can_navigate_forward());
5117// });
5118
5119// workspace
5120// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5121// .await
5122// .unwrap();
5123
5124// assert_eq!(*toolbar_notify_count.borrow(), 3);
5125// pane.read_with(cx, |pane, _| {
5126// assert!(!pane.can_navigate_backward());
5127// assert!(pane.can_navigate_forward());
5128// });
5129// }
5130
5131// #[gpui::test]
5132// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5133// init_test(cx);
5134// let fs = FakeFs::new(cx.background());
5135
5136// let project = Project::test(fs, [], cx).await;
5137// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5138// let workspace = window.root(cx);
5139
5140// let panel = workspace.update(cx, |workspace, cx| {
5141// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5142// workspace.add_panel(panel.clone(), cx);
5143
5144// workspace
5145// .right_dock()
5146// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5147
5148// panel
5149// });
5150
5151// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5152// pane.update(cx, |pane, cx| {
5153// let item = cx.build_view(|_| TestItem::new());
5154// pane.add_item(Box::new(item), true, true, None, cx);
5155// });
5156
5157// // Transfer focus from center to panel
5158// workspace.update(cx, |workspace, cx| {
5159// workspace.toggle_panel_focus::<TestPanel>(cx);
5160// });
5161
5162// workspace.read_with(cx, |workspace, cx| {
5163// assert!(workspace.right_dock().read(cx).is_open());
5164// assert!(!panel.is_zoomed(cx));
5165// assert!(panel.has_focus(cx));
5166// });
5167
5168// // Transfer focus from panel to center
5169// workspace.update(cx, |workspace, cx| {
5170// workspace.toggle_panel_focus::<TestPanel>(cx);
5171// });
5172
5173// workspace.read_with(cx, |workspace, cx| {
5174// assert!(workspace.right_dock().read(cx).is_open());
5175// assert!(!panel.is_zoomed(cx));
5176// assert!(!panel.has_focus(cx));
5177// });
5178
5179// // Close the dock
5180// workspace.update(cx, |workspace, cx| {
5181// workspace.toggle_dock(DockPosition::Right, cx);
5182// });
5183
5184// workspace.read_with(cx, |workspace, cx| {
5185// assert!(!workspace.right_dock().read(cx).is_open());
5186// assert!(!panel.is_zoomed(cx));
5187// assert!(!panel.has_focus(cx));
5188// });
5189
5190// // Open the dock
5191// workspace.update(cx, |workspace, cx| {
5192// workspace.toggle_dock(DockPosition::Right, cx);
5193// });
5194
5195// workspace.read_with(cx, |workspace, cx| {
5196// assert!(workspace.right_dock().read(cx).is_open());
5197// assert!(!panel.is_zoomed(cx));
5198// assert!(panel.has_focus(cx));
5199// });
5200
5201// // Focus and zoom panel
5202// panel.update(cx, |panel, cx| {
5203// cx.focus_self();
5204// panel.set_zoomed(true, cx)
5205// });
5206
5207// workspace.read_with(cx, |workspace, cx| {
5208// assert!(workspace.right_dock().read(cx).is_open());
5209// assert!(panel.is_zoomed(cx));
5210// assert!(panel.has_focus(cx));
5211// });
5212
5213// // Transfer focus to the center closes the dock
5214// workspace.update(cx, |workspace, cx| {
5215// workspace.toggle_panel_focus::<TestPanel>(cx);
5216// });
5217
5218// workspace.read_with(cx, |workspace, cx| {
5219// assert!(!workspace.right_dock().read(cx).is_open());
5220// assert!(panel.is_zoomed(cx));
5221// assert!(!panel.has_focus(cx));
5222// });
5223
5224// // Transferring focus back to the panel keeps it zoomed
5225// workspace.update(cx, |workspace, cx| {
5226// workspace.toggle_panel_focus::<TestPanel>(cx);
5227// });
5228
5229// workspace.read_with(cx, |workspace, cx| {
5230// assert!(workspace.right_dock().read(cx).is_open());
5231// assert!(panel.is_zoomed(cx));
5232// assert!(panel.has_focus(cx));
5233// });
5234
5235// // Close the dock while it is zoomed
5236// workspace.update(cx, |workspace, cx| {
5237// workspace.toggle_dock(DockPosition::Right, cx)
5238// });
5239
5240// workspace.read_with(cx, |workspace, cx| {
5241// assert!(!workspace.right_dock().read(cx).is_open());
5242// assert!(panel.is_zoomed(cx));
5243// assert!(workspace.zoomed.is_none());
5244// assert!(!panel.has_focus(cx));
5245// });
5246
5247// // Opening the dock, when it's zoomed, retains focus
5248// workspace.update(cx, |workspace, cx| {
5249// workspace.toggle_dock(DockPosition::Right, cx)
5250// });
5251
5252// workspace.read_with(cx, |workspace, cx| {
5253// assert!(workspace.right_dock().read(cx).is_open());
5254// assert!(panel.is_zoomed(cx));
5255// assert!(workspace.zoomed.is_some());
5256// assert!(panel.has_focus(cx));
5257// });
5258
5259// // Unzoom and close the panel, zoom the active pane.
5260// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5261// workspace.update(cx, |workspace, cx| {
5262// workspace.toggle_dock(DockPosition::Right, cx)
5263// });
5264// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5265
5266// // Opening a dock unzooms the pane.
5267// workspace.update(cx, |workspace, cx| {
5268// workspace.toggle_dock(DockPosition::Right, cx)
5269// });
5270// workspace.read_with(cx, |workspace, cx| {
5271// let pane = pane.read(cx);
5272// assert!(!pane.is_zoomed());
5273// assert!(!pane.has_focus());
5274// assert!(workspace.right_dock().read(cx).is_open());
5275// assert!(workspace.zoomed.is_none());
5276// });
5277// }
5278
5279// #[gpui::test]
5280// async fn test_panels(cx: &mut gpui::TestAppContext) {
5281// init_test(cx);
5282// let fs = FakeFs::new(cx.background());
5283
5284// let project = Project::test(fs, [], cx).await;
5285// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5286// let workspace = window.root(cx);
5287
5288// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5289// // Add panel_1 on the left, panel_2 on the right.
5290// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5291// workspace.add_panel(panel_1.clone(), cx);
5292// workspace
5293// .left_dock()
5294// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5295// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5296// workspace.add_panel(panel_2.clone(), cx);
5297// workspace
5298// .right_dock()
5299// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5300
5301// let left_dock = workspace.left_dock();
5302// assert_eq!(
5303// left_dock.read(cx).visible_panel().unwrap().id(),
5304// panel_1.id()
5305// );
5306// assert_eq!(
5307// left_dock.read(cx).active_panel_size(cx).unwrap(),
5308// panel_1.size(cx)
5309// );
5310
5311// left_dock.update(cx, |left_dock, cx| {
5312// left_dock.resize_active_panel(Some(1337.), cx)
5313// });
5314// assert_eq!(
5315// workspace
5316// .right_dock()
5317// .read(cx)
5318// .visible_panel()
5319// .unwrap()
5320// .id(),
5321// panel_2.id()
5322// );
5323
5324// (panel_1, panel_2)
5325// });
5326
5327// // Move panel_1 to the right
5328// panel_1.update(cx, |panel_1, cx| {
5329// panel_1.set_position(DockPosition::Right, cx)
5330// });
5331
5332// workspace.update(cx, |workspace, cx| {
5333// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5334// // Since it was the only panel on the left, the left dock should now be closed.
5335// assert!(!workspace.left_dock().read(cx).is_open());
5336// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5337// let right_dock = workspace.right_dock();
5338// assert_eq!(
5339// right_dock.read(cx).visible_panel().unwrap().id(),
5340// panel_1.id()
5341// );
5342// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5343
5344// // Now we move panel_2Β to the left
5345// panel_2.set_position(DockPosition::Left, cx);
5346// });
5347
5348// workspace.update(cx, |workspace, cx| {
5349// // Since panel_2 was not visible on the right, we don't open the left dock.
5350// assert!(!workspace.left_dock().read(cx).is_open());
5351// // And the right dock is unaffected in it's displaying of panel_1
5352// assert!(workspace.right_dock().read(cx).is_open());
5353// assert_eq!(
5354// workspace
5355// .right_dock()
5356// .read(cx)
5357// .visible_panel()
5358// .unwrap()
5359// .id(),
5360// panel_1.id()
5361// );
5362// });
5363
5364// // Move panel_1 back to the left
5365// panel_1.update(cx, |panel_1, cx| {
5366// panel_1.set_position(DockPosition::Left, cx)
5367// });
5368
5369// workspace.update(cx, |workspace, cx| {
5370// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5371// let left_dock = workspace.left_dock();
5372// assert!(left_dock.read(cx).is_open());
5373// assert_eq!(
5374// left_dock.read(cx).visible_panel().unwrap().id(),
5375// panel_1.id()
5376// );
5377// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5378// // And right the dock should be closed as it no longer has any panels.
5379// assert!(!workspace.right_dock().read(cx).is_open());
5380
5381// // Now we move panel_1 to the bottom
5382// panel_1.set_position(DockPosition::Bottom, cx);
5383// });
5384
5385// workspace.update(cx, |workspace, cx| {
5386// // Since panel_1 was visible on the left, we close the left dock.
5387// assert!(!workspace.left_dock().read(cx).is_open());
5388// // The bottom dock is sized based on the panel's default size,
5389// // since the panel orientation changed from vertical to horizontal.
5390// let bottom_dock = workspace.bottom_dock();
5391// assert_eq!(
5392// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5393// panel_1.size(cx),
5394// );
5395// // Close bottom dock and move panel_1 back to the left.
5396// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5397// panel_1.set_position(DockPosition::Left, cx);
5398// });
5399
5400// // Emit activated event on panel 1
5401// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5402
5403// // Now the left dock is open and panel_1 is active and focused.
5404// workspace.read_with(cx, |workspace, cx| {
5405// let left_dock = workspace.left_dock();
5406// assert!(left_dock.read(cx).is_open());
5407// assert_eq!(
5408// left_dock.read(cx).visible_panel().unwrap().id(),
5409// panel_1.id()
5410// );
5411// assert!(panel_1.is_focused(cx));
5412// });
5413
5414// // Emit closed event on panel 2, which is not active
5415// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5416
5417// // Wo don't close the left dock, because panel_2 wasn't the active panel
5418// workspace.read_with(cx, |workspace, cx| {
5419// let left_dock = workspace.left_dock();
5420// assert!(left_dock.read(cx).is_open());
5421// assert_eq!(
5422// left_dock.read(cx).visible_panel().unwrap().id(),
5423// panel_1.id()
5424// );
5425// });
5426
5427// // Emitting a ZoomIn event shows the panel as zoomed.
5428// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5429// workspace.read_with(cx, |workspace, _| {
5430// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5431// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5432// });
5433
5434// // Move panel to another dock while it is zoomed
5435// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5436// workspace.read_with(cx, |workspace, _| {
5437// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5438// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5439// });
5440
5441// // If focus is transferred to another view that's not a panel or another pane, we still show
5442// // the panel as zoomed.
5443// let focus_receiver = window.build_view(cx, |_| EmptyView);
5444// focus_receiver.update(cx, |_, cx| cx.focus_self());
5445// workspace.read_with(cx, |workspace, _| {
5446// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5447// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5448// });
5449
5450// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5451// workspace.update(cx, |_, cx| cx.focus_self());
5452// workspace.read_with(cx, |workspace, _| {
5453// assert_eq!(workspace.zoomed, None);
5454// assert_eq!(workspace.zoomed_position, None);
5455// });
5456
5457// // If focus is transferred again to another view that's not a panel or a pane, we won't
5458// // show the panel as zoomed because it wasn't zoomed before.
5459// focus_receiver.update(cx, |_, cx| cx.focus_self());
5460// workspace.read_with(cx, |workspace, _| {
5461// assert_eq!(workspace.zoomed, None);
5462// assert_eq!(workspace.zoomed_position, None);
5463// });
5464
5465// // When focus is transferred back to the panel, it is zoomed again.
5466// panel_1.update(cx, |_, cx| cx.focus_self());
5467// workspace.read_with(cx, |workspace, _| {
5468// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5469// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5470// });
5471
5472// // Emitting a ZoomOut event unzooms the panel.
5473// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5474// workspace.read_with(cx, |workspace, _| {
5475// assert_eq!(workspace.zoomed, None);
5476// assert_eq!(workspace.zoomed_position, None);
5477// });
5478
5479// // Emit closed event on panel 1, which is active
5480// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5481
5482// // Now the left dock is closed, because panel_1 was the active panel
5483// workspace.read_with(cx, |workspace, cx| {
5484// let right_dock = workspace.right_dock();
5485// assert!(!right_dock.read(cx).is_open());
5486// });
5487// }
5488
5489// pub fn init_test(cx: &mut TestAppContext) {
5490// cx.foreground().forbid_parking();
5491// cx.update(|cx| {
5492// cx.set_global(SettingsStore::test(cx));
5493// theme::init((), cx);
5494// language::init(cx);
5495// crate::init_settings(cx);
5496// Project::init_settings(cx);
5497// });
5498// }
5499// }