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(cx) {
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(self.notifications.iter().map(|(_, _, notification)| {
2644 div()
2645 .on_any_mouse_down(|_, cx| cx.stop_propagation())
2646 .on_any_mouse_up(|_, cx| cx.stop_propagation())
2647 .child(notification.to_any())
2648 })),
2649 )
2650 }
2651 }
2652
2653 // // RPC handlers
2654
2655 fn handle_follow(
2656 &mut self,
2657 _follower_project_id: Option<u64>,
2658 _cx: &mut ViewContext<Self>,
2659 ) -> proto::FollowResponse {
2660 todo!()
2661
2662 // let client = &self.app_state.client;
2663 // let project_id = self.project.read(cx).remote_id();
2664
2665 // let active_view_id = self.active_item(cx).and_then(|i| {
2666 // Some(
2667 // i.to_followable_item_handle(cx)?
2668 // .remote_id(client, cx)?
2669 // .to_proto(),
2670 // )
2671 // });
2672
2673 // cx.notify();
2674
2675 // self.last_active_view_id = active_view_id.clone();
2676 // proto::FollowResponse {
2677 // active_view_id,
2678 // views: self
2679 // .panes()
2680 // .iter()
2681 // .flat_map(|pane| {
2682 // let leader_id = self.leader_for_pane(pane);
2683 // pane.read(cx).items().filter_map({
2684 // let cx = &cx;
2685 // move |item| {
2686 // let item = item.to_followable_item_handle(cx)?;
2687 // if (project_id.is_none() || project_id != follower_project_id)
2688 // && item.is_project_item(cx)
2689 // {
2690 // return None;
2691 // }
2692 // let id = item.remote_id(client, cx)?.to_proto();
2693 // let variant = item.to_state_proto(cx)?;
2694 // Some(proto::View {
2695 // id: Some(id),
2696 // leader_id,
2697 // variant: Some(variant),
2698 // })
2699 // }
2700 // })
2701 // })
2702 // .collect(),
2703 // }
2704 }
2705
2706 fn handle_update_followers(
2707 &mut self,
2708 leader_id: PeerId,
2709 message: proto::UpdateFollowers,
2710 _cx: &mut ViewContext<Self>,
2711 ) {
2712 self.leader_updates_tx
2713 .unbounded_send((leader_id, message))
2714 .ok();
2715 }
2716
2717 async fn process_leader_update(
2718 this: &WeakView<Self>,
2719 leader_id: PeerId,
2720 update: proto::UpdateFollowers,
2721 cx: &mut AsyncWindowContext,
2722 ) -> Result<()> {
2723 match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2724 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2725 this.update(cx, |this, _| {
2726 for (_, state) in &mut this.follower_states {
2727 if state.leader_id == leader_id {
2728 state.active_view_id =
2729 if let Some(active_view_id) = update_active_view.id.clone() {
2730 Some(ViewId::from_proto(active_view_id)?)
2731 } else {
2732 None
2733 };
2734 }
2735 }
2736 anyhow::Ok(())
2737 })??;
2738 }
2739 proto::update_followers::Variant::UpdateView(update_view) => {
2740 let variant = update_view
2741 .variant
2742 .ok_or_else(|| anyhow!("missing update view variant"))?;
2743 let id = update_view
2744 .id
2745 .ok_or_else(|| anyhow!("missing update view id"))?;
2746 let mut tasks = Vec::new();
2747 this.update(cx, |this, cx| {
2748 let project = this.project.clone();
2749 for (_, state) in &mut this.follower_states {
2750 if state.leader_id == leader_id {
2751 let view_id = ViewId::from_proto(id.clone())?;
2752 if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2753 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2754 }
2755 }
2756 }
2757 anyhow::Ok(())
2758 })??;
2759 try_join_all(tasks).await.log_err();
2760 }
2761 proto::update_followers::Variant::CreateView(view) => {
2762 let panes = this.update(cx, |this, _| {
2763 this.follower_states
2764 .iter()
2765 .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2766 .cloned()
2767 .collect()
2768 })?;
2769 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2770 }
2771 }
2772 this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2773 Ok(())
2774 }
2775
2776 async fn add_views_from_leader(
2777 this: WeakView<Self>,
2778 leader_id: PeerId,
2779 panes: Vec<View<Pane>>,
2780 views: Vec<proto::View>,
2781 cx: &mut AsyncWindowContext,
2782 ) -> Result<()> {
2783 let this = this.upgrade().context("workspace dropped")?;
2784
2785 let item_builders = cx.update(|_, cx| {
2786 cx.default_global::<FollowableItemBuilders>()
2787 .values()
2788 .map(|b| b.0)
2789 .collect::<Vec<_>>()
2790 })?;
2791
2792 let mut item_tasks_by_pane = HashMap::default();
2793 for pane in panes {
2794 let mut item_tasks = Vec::new();
2795 let mut leader_view_ids = Vec::new();
2796 for view in &views {
2797 let Some(id) = &view.id else { continue };
2798 let id = ViewId::from_proto(id.clone())?;
2799 let mut variant = view.variant.clone();
2800 if variant.is_none() {
2801 Err(anyhow!("missing view variant"))?;
2802 }
2803 for build_item in &item_builders {
2804 let task = cx.update(|_, cx| {
2805 build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2806 })?;
2807 if let Some(task) = task {
2808 item_tasks.push(task);
2809 leader_view_ids.push(id);
2810 break;
2811 } else {
2812 assert!(variant.is_some());
2813 }
2814 }
2815 }
2816
2817 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2818 }
2819
2820 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2821 let items = futures::future::try_join_all(item_tasks).await?;
2822 this.update(cx, |this, cx| {
2823 let state = this.follower_states.get_mut(&pane)?;
2824 for (id, item) in leader_view_ids.into_iter().zip(items) {
2825 item.set_leader_peer_id(Some(leader_id), cx);
2826 state.items_by_leader_view_id.insert(id, item);
2827 }
2828
2829 Some(())
2830 })?;
2831 }
2832 Ok(())
2833 }
2834
2835 fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2836 let mut is_project_item = true;
2837 let mut update = proto::UpdateActiveView::default();
2838 if self.active_pane.read(cx).has_focus(cx) {
2839 let item = self
2840 .active_item(cx)
2841 .and_then(|item| item.to_followable_item_handle(cx));
2842 if let Some(item) = item {
2843 is_project_item = item.is_project_item(cx);
2844 update = proto::UpdateActiveView {
2845 id: item
2846 .remote_id(&self.app_state.client, cx)
2847 .map(|id| id.to_proto()),
2848 leader_id: self.leader_for_pane(&self.active_pane),
2849 };
2850 }
2851 }
2852
2853 if update.id != self.last_active_view_id {
2854 self.last_active_view_id = update.id.clone();
2855 self.update_followers(
2856 is_project_item,
2857 proto::update_followers::Variant::UpdateActiveView(update),
2858 cx,
2859 );
2860 }
2861 }
2862
2863 fn update_followers(
2864 &self,
2865 project_only: bool,
2866 update: proto::update_followers::Variant,
2867 cx: &mut WindowContext,
2868 ) -> Option<()> {
2869 let project_id = if project_only {
2870 self.project.read(cx).remote_id()
2871 } else {
2872 None
2873 };
2874 let room_id = self.call_handler.room_id(cx)?;
2875 self.app_state().workspace_store.update(cx, |store, cx| {
2876 store.update_followers(project_id, room_id, update, cx)
2877 })
2878 }
2879
2880 pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2881 self.follower_states.get(pane).map(|state| state.leader_id)
2882 }
2883
2884 pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2885 cx.notify();
2886
2887 let (leader_in_this_project, leader_in_this_app) =
2888 self.call_handler.peer_state(leader_id, cx)?;
2889 let mut items_to_activate = Vec::new();
2890 for (pane, state) in &self.follower_states {
2891 if state.leader_id != leader_id {
2892 continue;
2893 }
2894 if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2895 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2896 if leader_in_this_project || !item.is_project_item(cx) {
2897 items_to_activate.push((pane.clone(), item.boxed_clone()));
2898 }
2899 } else {
2900 log::warn!(
2901 "unknown view id {:?} for leader {:?}",
2902 active_view_id,
2903 leader_id
2904 );
2905 }
2906 continue;
2907 }
2908
2909 if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2910 items_to_activate.push((pane.clone(), shared_screen));
2911 }
2912 }
2913
2914 for (pane, item) in items_to_activate {
2915 let pane_was_focused = pane.read(cx).has_focus(cx);
2916 if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2917 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2918 } else {
2919 pane.update(cx, |pane, mut cx| {
2920 pane.add_item(item.boxed_clone(), false, false, None, &mut cx)
2921 });
2922 }
2923
2924 if pane_was_focused {
2925 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2926 }
2927 }
2928
2929 None
2930 }
2931
2932 fn shared_screen_for_peer(
2933 &self,
2934 peer_id: PeerId,
2935 pane: &View<Pane>,
2936 cx: &mut ViewContext<Self>,
2937 ) -> Option<Box<dyn ItemHandle>> {
2938 self.call_handler.shared_screen_for_peer(peer_id, pane, cx)
2939 // let call = self.active_call()?;
2940 // let room = call.read(cx).room()?.read(cx);
2941 // let participant = room.remote_participant_for_peer_id(peer_id)?;
2942 // let track = participant.video_tracks.values().next()?.clone();
2943 // let user = participant.user.clone();
2944
2945 // for item in pane.read(cx).items_of_type::<SharedScreen>() {
2946 // if item.read(cx).peer_id == peer_id {
2947 // return Some(item);
2948 // }
2949 // }
2950
2951 // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2952 }
2953
2954 pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2955 if cx.is_window_active() {
2956 self.update_active_view_for_followers(cx);
2957 cx.background_executor()
2958 .spawn(persistence::DB.update_timestamp(self.database_id()))
2959 .detach();
2960 } else {
2961 for pane in &self.panes {
2962 pane.update(cx, |pane, cx| {
2963 if let Some(item) = pane.active_item() {
2964 item.workspace_deactivated(cx);
2965 }
2966 if matches!(
2967 WorkspaceSettings::get_global(cx).autosave,
2968 AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2969 ) {
2970 for item in pane.items() {
2971 Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2972 .detach_and_log_err(cx);
2973 }
2974 }
2975 });
2976 }
2977 }
2978 }
2979
2980 pub fn database_id(&self) -> WorkspaceId {
2981 self.database_id
2982 }
2983
2984 fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2985 let project = self.project().read(cx);
2986
2987 if project.is_local() {
2988 Some(
2989 project
2990 .visible_worktrees(cx)
2991 .map(|worktree| worktree.read(cx).abs_path())
2992 .collect::<Vec<_>>()
2993 .into(),
2994 )
2995 } else {
2996 None
2997 }
2998 }
2999
3000 fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
3001 match member {
3002 Member::Axis(PaneAxis { members, .. }) => {
3003 for child in members.iter() {
3004 self.remove_panes(child.clone(), cx)
3005 }
3006 }
3007 Member::Pane(pane) => {
3008 self.force_remove_pane(&pane, cx);
3009 }
3010 }
3011 }
3012
3013 fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
3014 self.panes.retain(|p| p != pane);
3015 self.panes
3016 .last()
3017 .unwrap()
3018 .update(cx, |pane, cx| pane.focus(cx));
3019 if self.last_active_center_pane == Some(pane.downgrade()) {
3020 self.last_active_center_pane = None;
3021 }
3022 cx.notify();
3023 }
3024
3025 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
3026 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
3027 cx.background_executor()
3028 .timer(Duration::from_millis(100))
3029 .await;
3030 this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
3031 .log_err();
3032 }));
3033 }
3034
3035 fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
3036 fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
3037 let (items, active) = {
3038 let pane = pane_handle.read(cx);
3039 let active_item_id = pane.active_item().map(|item| item.item_id());
3040 (
3041 pane.items()
3042 .filter_map(|item_handle| {
3043 Some(SerializedItem {
3044 kind: Arc::from(item_handle.serialized_item_kind()?),
3045 item_id: item_handle.item_id().as_u64(),
3046 active: Some(item_handle.item_id()) == active_item_id,
3047 })
3048 })
3049 .collect::<Vec<_>>(),
3050 pane.has_focus(cx),
3051 )
3052 };
3053
3054 SerializedPane::new(items, active)
3055 }
3056
3057 fn build_serialized_pane_group(
3058 pane_group: &Member,
3059 cx: &WindowContext,
3060 ) -> SerializedPaneGroup {
3061 match pane_group {
3062 Member::Axis(PaneAxis {
3063 axis,
3064 members,
3065 flexes,
3066 bounding_boxes: _,
3067 }) => SerializedPaneGroup::Group {
3068 axis: *axis,
3069 children: members
3070 .iter()
3071 .map(|member| build_serialized_pane_group(member, cx))
3072 .collect::<Vec<_>>(),
3073 flexes: Some(flexes.lock().clone()),
3074 },
3075 Member::Pane(pane_handle) => {
3076 SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3077 }
3078 }
3079 }
3080
3081 fn build_serialized_docks(
3082 this: &Workspace,
3083 cx: &mut ViewContext<Workspace>,
3084 ) -> DockStructure {
3085 let left_dock = this.left_dock.read(cx);
3086 let left_visible = left_dock.is_open();
3087 let left_active_panel = left_dock
3088 .visible_panel()
3089 .and_then(|panel| Some(panel.persistent_name().to_string()));
3090 let left_dock_zoom = left_dock
3091 .visible_panel()
3092 .map(|panel| panel.is_zoomed(cx))
3093 .unwrap_or(false);
3094
3095 let right_dock = this.right_dock.read(cx);
3096 let right_visible = right_dock.is_open();
3097 let right_active_panel = right_dock
3098 .visible_panel()
3099 .and_then(|panel| Some(panel.persistent_name().to_string()));
3100 let right_dock_zoom = right_dock
3101 .visible_panel()
3102 .map(|panel| panel.is_zoomed(cx))
3103 .unwrap_or(false);
3104
3105 let bottom_dock = this.bottom_dock.read(cx);
3106 let bottom_visible = bottom_dock.is_open();
3107 let bottom_active_panel = bottom_dock
3108 .visible_panel()
3109 .and_then(|panel| Some(panel.persistent_name().to_string()));
3110 let bottom_dock_zoom = bottom_dock
3111 .visible_panel()
3112 .map(|panel| panel.is_zoomed(cx))
3113 .unwrap_or(false);
3114
3115 DockStructure {
3116 left: DockData {
3117 visible: left_visible,
3118 active_panel: left_active_panel,
3119 zoom: left_dock_zoom,
3120 },
3121 right: DockData {
3122 visible: right_visible,
3123 active_panel: right_active_panel,
3124 zoom: right_dock_zoom,
3125 },
3126 bottom: DockData {
3127 visible: bottom_visible,
3128 active_panel: bottom_active_panel,
3129 zoom: bottom_dock_zoom,
3130 },
3131 }
3132 }
3133
3134 if let Some(location) = self.location(cx) {
3135 // Load bearing special case:
3136 // - with_local_workspace() relies on this to not have other stuff open
3137 // when you open your log
3138 if !location.paths().is_empty() {
3139 let center_group = build_serialized_pane_group(&self.center.root, cx);
3140 let docks = build_serialized_docks(self, cx);
3141
3142 let serialized_workspace = SerializedWorkspace {
3143 id: self.database_id,
3144 location,
3145 center_group,
3146 bounds: Default::default(),
3147 display: Default::default(),
3148 docks,
3149 };
3150
3151 cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3152 .detach();
3153 }
3154 }
3155 }
3156
3157 pub(crate) fn load_workspace(
3158 serialized_workspace: SerializedWorkspace,
3159 paths_to_open: Vec<Option<ProjectPath>>,
3160 cx: &mut ViewContext<Workspace>,
3161 ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3162 cx.spawn(|workspace, mut cx| async move {
3163 let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3164 (
3165 workspace.project().clone(),
3166 workspace.last_active_center_pane.clone(),
3167 )
3168 })?;
3169
3170 let mut center_group = None;
3171 let mut center_items = None;
3172
3173 // Traverse the splits tree and add to things
3174 if let Some((group, active_pane, items)) = serialized_workspace
3175 .center_group
3176 .deserialize(
3177 &project,
3178 serialized_workspace.id,
3179 workspace.clone(),
3180 &mut cx,
3181 )
3182 .await
3183 {
3184 center_items = Some(items);
3185 center_group = Some((group, active_pane))
3186 }
3187
3188 let mut items_by_project_path = cx.update(|_, cx| {
3189 center_items
3190 .unwrap_or_default()
3191 .into_iter()
3192 .filter_map(|item| {
3193 let item = item?;
3194 let project_path = item.project_path(cx)?;
3195 Some((project_path, item))
3196 })
3197 .collect::<HashMap<_, _>>()
3198 })?;
3199
3200 let opened_items = paths_to_open
3201 .into_iter()
3202 .map(|path_to_open| {
3203 path_to_open
3204 .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3205 })
3206 .collect::<Vec<_>>();
3207
3208 // Remove old panes from workspace panes list
3209 workspace.update(&mut cx, |workspace, cx| {
3210 if let Some((center_group, active_pane)) = center_group {
3211 workspace.remove_panes(workspace.center.root.clone(), cx);
3212
3213 // Swap workspace center group
3214 workspace.center = PaneGroup::with_root(center_group);
3215 workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3216 if let Some(active_pane) = active_pane {
3217 workspace.active_pane = active_pane;
3218 cx.focus_self();
3219 } else {
3220 workspace.active_pane = workspace.center.first_pane().clone();
3221 }
3222 }
3223
3224 let docks = serialized_workspace.docks;
3225 workspace.left_dock.update(cx, |dock, cx| {
3226 dock.set_open(docks.left.visible, cx);
3227 if let Some(active_panel) = docks.left.active_panel {
3228 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3229 dock.activate_panel(ix, cx);
3230 }
3231 }
3232 dock.active_panel()
3233 .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3234 if docks.left.visible && docks.left.zoom {
3235 cx.focus_self()
3236 }
3237 });
3238 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3239 workspace.right_dock.update(cx, |dock, cx| {
3240 dock.set_open(docks.right.visible, cx);
3241 if let Some(active_panel) = docks.right.active_panel {
3242 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3243 dock.activate_panel(ix, cx);
3244 }
3245 }
3246 dock.active_panel()
3247 .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3248
3249 if docks.right.visible && docks.right.zoom {
3250 cx.focus_self()
3251 }
3252 });
3253 workspace.bottom_dock.update(cx, |dock, cx| {
3254 dock.set_open(docks.bottom.visible, cx);
3255 if let Some(active_panel) = docks.bottom.active_panel {
3256 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3257 dock.activate_panel(ix, cx);
3258 }
3259 }
3260
3261 dock.active_panel()
3262 .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3263
3264 if docks.bottom.visible && docks.bottom.zoom {
3265 cx.focus_self()
3266 }
3267 });
3268
3269 cx.notify();
3270 })?;
3271
3272 // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3273 workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3274
3275 Ok(opened_items)
3276 })
3277 }
3278
3279 fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3280 self.add_workspace_actions_listeners(div, cx)
3281 // cx.add_async_action(Workspace::open);
3282 // cx.add_async_action(Workspace::follow_next_collaborator);
3283 // cx.add_async_action(Workspace::close);
3284 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3285 .on_action(cx.listener(Self::close_all_items_and_panes))
3286 // cx.add_global_action(Workspace::close_global);
3287 // cx.add_global_action(restart);
3288 .on_action(cx.listener(Self::save_all))
3289 .on_action(cx.listener(Self::add_folder_to_project))
3290 .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3291 let pane = workspace.active_pane().clone();
3292 workspace.unfollow(&pane, cx);
3293 }))
3294 .on_action(cx.listener(|workspace, action: &Save, cx| {
3295 workspace
3296 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3297 .detach_and_log_err(cx);
3298 }))
3299 .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3300 workspace
3301 .save_active_item(SaveIntent::SaveAs, cx)
3302 .detach_and_log_err(cx);
3303 }))
3304 .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3305 workspace.activate_previous_pane(cx)
3306 }))
3307 .on_action(
3308 cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3309 )
3310 .on_action(
3311 cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3312 workspace.activate_pane_in_direction(action.0, cx)
3313 }),
3314 )
3315 .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3316 workspace.swap_pane_in_direction(action.0, cx)
3317 }))
3318 .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3319 this.toggle_dock(DockPosition::Left, cx);
3320 }))
3321 .on_action(
3322 cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3323 workspace.toggle_dock(DockPosition::Right, cx);
3324 }),
3325 )
3326 .on_action(
3327 cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3328 workspace.toggle_dock(DockPosition::Bottom, cx);
3329 }),
3330 )
3331 .on_action(
3332 cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3333 workspace.close_all_docks(cx);
3334 }),
3335 )
3336 // cx.add_action(Workspace::activate_pane_at_index);
3337 // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3338 // workspace.reopen_closed_item(cx).detach();
3339 // });
3340 // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3341 // workspace
3342 // .go_back(workspace.active_pane().downgrade(), cx)
3343 // .detach();
3344 // });
3345 // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3346 // workspace
3347 // .go_forward(workspace.active_pane().downgrade(), cx)
3348 // .detach();
3349 // });
3350
3351 // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3352 // cx.spawn(|workspace, mut cx| async move {
3353 // let err = install_cli::install_cli(&cx)
3354 // .await
3355 // .context("Failed to create CLI symlink");
3356
3357 // workspace.update(&mut cx, |workspace, cx| {
3358 // if matches!(err, Err(_)) {
3359 // err.notify_err(workspace, cx);
3360 // } else {
3361 // workspace.show_notification(1, cx, |cx| {
3362 // cx.build_view(|_| {
3363 // MessageNotification::new("Successfully installed the `zed` binary")
3364 // })
3365 // });
3366 // }
3367 // })
3368 // })
3369 // .detach();
3370 // });
3371 }
3372
3373 #[cfg(any(test, feature = "test-support"))]
3374 pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3375 use node_runtime::FakeNodeRuntime;
3376
3377 let client = project.read(cx).client();
3378 let user_store = project.read(cx).user_store();
3379
3380 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3381 let app_state = Arc::new(AppState {
3382 languages: project.read(cx).languages().clone(),
3383 workspace_store,
3384 client,
3385 user_store,
3386 fs: project.read(cx).fs().clone(),
3387 build_window_options: |_, _, _| Default::default(),
3388 node_runtime: FakeNodeRuntime::new(),
3389 call_factory: |_, _| Box::new(TestCallHandler),
3390 });
3391 let workspace = Self::new(0, project, app_state, cx);
3392 workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3393 workspace
3394 }
3395
3396 // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3397 // let dock = match position {
3398 // DockPosition::Left => &self.left_dock,
3399 // DockPosition::Right => &self.right_dock,
3400 // DockPosition::Bottom => &self.bottom_dock,
3401 // };
3402 // let active_panel = dock.read(cx).visible_panel()?;
3403 // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3404 // dock.read(cx).render_placeholder(cx)
3405 // } else {
3406 // ChildView::new(dock, cx).into_any()
3407 // };
3408
3409 // Some(
3410 // element
3411 // .constrained()
3412 // .dynamically(move |constraint, _, cx| match position {
3413 // DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3414 // Vector2F::new(20., constraint.min.y()),
3415 // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3416 // ),
3417 // DockPosition::Bottom => SizeConstraint::new(
3418 // Vector2F::new(constraint.min.x(), 20.),
3419 // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3420 // ),
3421 // })
3422 // .into_any(),
3423 // )
3424 // }
3425 // }
3426 pub fn register_action<A: Action>(
3427 &mut self,
3428 callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3429 ) -> &mut Self {
3430 let callback = Arc::new(callback);
3431
3432 self.workspace_actions.push(Box::new(move |div, cx| {
3433 let callback = callback.clone();
3434 div.on_action(
3435 cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3436 )
3437 }));
3438 self
3439 }
3440
3441 fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3442 let mut div = div
3443 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3444 .on_action(cx.listener(Self::close_all_items_and_panes))
3445 .on_action(cx.listener(Self::add_folder_to_project))
3446 .on_action(cx.listener(Self::save_all))
3447 .on_action(cx.listener(Self::open));
3448 for action in self.workspace_actions.iter() {
3449 div = (action)(div, cx)
3450 }
3451 div
3452 }
3453
3454 pub fn active_modal<V: ManagedView + 'static>(
3455 &mut self,
3456 cx: &ViewContext<Self>,
3457 ) -> Option<View<V>> {
3458 self.modal_layer.read(cx).active_modal()
3459 }
3460
3461 pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3462 where
3463 B: FnOnce(&mut ViewContext<V>) -> V,
3464 {
3465 self.modal_layer
3466 .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3467 }
3468
3469 pub fn call_state(&mut self) -> &mut dyn CallHandler {
3470 &mut *self.call_handler
3471 }
3472}
3473
3474fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3475 let display_origin = cx
3476 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3477 .ok()??;
3478 ZED_WINDOW_POSITION
3479 .zip(*ZED_WINDOW_SIZE)
3480 .map(|(position, size)| {
3481 WindowBounds::Fixed(Bounds {
3482 origin: display_origin + position,
3483 size,
3484 })
3485 })
3486}
3487
3488fn open_items(
3489 serialized_workspace: Option<SerializedWorkspace>,
3490 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3491 app_state: Arc<AppState>,
3492 cx: &mut ViewContext<Workspace>,
3493) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3494 let restored_items = serialized_workspace.map(|serialized_workspace| {
3495 Workspace::load_workspace(
3496 serialized_workspace,
3497 project_paths_to_open
3498 .iter()
3499 .map(|(_, project_path)| project_path)
3500 .cloned()
3501 .collect(),
3502 cx,
3503 )
3504 });
3505
3506 cx.spawn(|workspace, mut cx| async move {
3507 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3508
3509 if let Some(restored_items) = restored_items {
3510 let restored_items = restored_items.await?;
3511
3512 let restored_project_paths = restored_items
3513 .iter()
3514 .filter_map(|item| {
3515 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3516 .ok()
3517 .flatten()
3518 })
3519 .collect::<HashSet<_>>();
3520
3521 for restored_item in restored_items {
3522 opened_items.push(restored_item.map(Ok));
3523 }
3524
3525 project_paths_to_open
3526 .iter_mut()
3527 .for_each(|(_, project_path)| {
3528 if let Some(project_path_to_open) = project_path {
3529 if restored_project_paths.contains(project_path_to_open) {
3530 *project_path = None;
3531 }
3532 }
3533 });
3534 } else {
3535 for _ in 0..project_paths_to_open.len() {
3536 opened_items.push(None);
3537 }
3538 }
3539 assert!(opened_items.len() == project_paths_to_open.len());
3540
3541 let tasks =
3542 project_paths_to_open
3543 .into_iter()
3544 .enumerate()
3545 .map(|(i, (abs_path, project_path))| {
3546 let workspace = workspace.clone();
3547 cx.spawn(|mut cx| {
3548 let fs = app_state.fs.clone();
3549 async move {
3550 let file_project_path = project_path?;
3551 if fs.is_file(&abs_path).await {
3552 Some((
3553 i,
3554 workspace
3555 .update(&mut cx, |workspace, cx| {
3556 workspace.open_path(file_project_path, None, true, cx)
3557 })
3558 .log_err()?
3559 .await,
3560 ))
3561 } else {
3562 None
3563 }
3564 }
3565 })
3566 });
3567
3568 let tasks = tasks.collect::<Vec<_>>();
3569
3570 let tasks = futures::future::join_all(tasks.into_iter());
3571 for maybe_opened_path in tasks.await.into_iter() {
3572 if let Some((i, path_open_result)) = maybe_opened_path {
3573 opened_items[i] = Some(path_open_result);
3574 }
3575 }
3576
3577 Ok(opened_items)
3578 })
3579}
3580
3581// todo!()
3582// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3583// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3584// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3585// const MESSAGE_ID: usize = 2;
3586
3587// if workspace
3588// .read_with(cx, |workspace, cx| {
3589// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3590// })
3591// .unwrap_or(false)
3592// {
3593// return;
3594// }
3595
3596// if db::kvp::KEY_VALUE_STORE
3597// .read_kvp(NEW_DOCK_HINT_KEY)
3598// .ok()
3599// .flatten()
3600// .is_some()
3601// {
3602// if !workspace
3603// .read_with(cx, |workspace, cx| {
3604// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3605// })
3606// .unwrap_or(false)
3607// {
3608// cx.update(|cx| {
3609// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3610// let entry = tracker
3611// .entry(TypeId::of::<MessageNotification>())
3612// .or_default();
3613// if !entry.contains(&MESSAGE_ID) {
3614// entry.push(MESSAGE_ID);
3615// }
3616// });
3617// });
3618// }
3619
3620// return;
3621// }
3622
3623// cx.spawn(|_| async move {
3624// db::kvp::KEY_VALUE_STORE
3625// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3626// .await
3627// .ok();
3628// })
3629// .detach();
3630
3631// workspace
3632// .update(cx, |workspace, cx| {
3633// workspace.show_notification_once(2, cx, |cx| {
3634// cx.build_view(|_| {
3635// MessageNotification::new_element(|text, _| {
3636// Text::new(
3637// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3638// text,
3639// )
3640// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3641// let code_span_background_color = settings::get::<ThemeSettings>(cx)
3642// .theme
3643// .editor
3644// .document_highlight_read_background;
3645
3646// cx.scene().push_quad(gpui::Quad {
3647// bounds,
3648// background: Some(code_span_background_color),
3649// border: Default::default(),
3650// corner_radii: (2.0).into(),
3651// })
3652// })
3653// .into_any()
3654// })
3655// .with_click_message("Read more about the new panel system")
3656// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3657// })
3658// })
3659// })
3660// .ok();
3661
3662fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3663 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3664
3665 workspace
3666 .update(cx, |workspace, cx| {
3667 if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3668 workspace.show_notification_once(0, cx, |cx| {
3669 cx.build_view(|_| {
3670 MessageNotification::new("Failed to load the database file.")
3671 .with_click_message("Click to let us know about this error")
3672 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3673 })
3674 });
3675 }
3676 })
3677 .log_err();
3678}
3679
3680impl FocusableView for Workspace {
3681 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3682 self.active_pane.focus_handle(cx)
3683 }
3684}
3685
3686impl Render for Workspace {
3687 type Element = Div;
3688
3689 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3690 let mut context = KeyContext::default();
3691 context.add("Workspace");
3692
3693 let (ui_font, ui_font_size) = {
3694 let theme_settings = ThemeSettings::get_global(cx);
3695 (
3696 theme_settings.ui_font.family.clone(),
3697 theme_settings.ui_font_size.clone(),
3698 )
3699 };
3700
3701 cx.set_rem_size(ui_font_size);
3702
3703 self.actions(div(), cx)
3704 .key_context(context)
3705 .relative()
3706 .size_full()
3707 .flex()
3708 .flex_col()
3709 .font(ui_font)
3710 .gap_0()
3711 .justify_start()
3712 .items_start()
3713 .text_color(cx.theme().colors().text)
3714 .bg(cx.theme().colors().background)
3715 .children(self.titlebar_item.clone())
3716 .child(
3717 div()
3718 .id("workspace")
3719 .relative()
3720 .flex_1()
3721 .w_full()
3722 .flex()
3723 .overflow_hidden()
3724 .border_t()
3725 .border_b()
3726 .border_color(cx.theme().colors().border)
3727 .child(self.modal_layer.clone())
3728 .child(
3729 div()
3730 .flex()
3731 .flex_row()
3732 .flex_1()
3733 .h_full()
3734 // Left Dock
3735 .child(
3736 div()
3737 .flex()
3738 .flex_none()
3739 .overflow_hidden()
3740 .child(self.left_dock.clone()),
3741 )
3742 // Panes
3743 .child(
3744 div()
3745 .flex()
3746 .flex_col()
3747 .flex_1()
3748 .child(self.center.render(
3749 &self.project,
3750 &self.follower_states,
3751 &self.active_pane,
3752 self.zoomed.as_ref(),
3753 &self.app_state,
3754 cx,
3755 ))
3756 .child(self.bottom_dock.clone()),
3757 )
3758 // Right Dock
3759 .child(
3760 div()
3761 .flex()
3762 .flex_none()
3763 .overflow_hidden()
3764 .child(self.right_dock.clone()),
3765 ),
3766 )
3767 .children(self.render_notifications(cx)),
3768 )
3769 .child(self.status_bar.clone())
3770 }
3771}
3772
3773// impl View for Workspace {
3774
3775// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3776// let theme = theme::current(cx).clone();
3777// Stack::new()
3778// .with_child(
3779// Flex::column()
3780// .with_child(self.render_titlebar(&theme, cx))
3781// .with_child(
3782// Stack::new()
3783// .with_child({
3784// let project = self.project.clone();
3785// Flex::row()
3786// .with_children(self.render_dock(DockPosition::Left, cx))
3787// .with_child(
3788// Flex::column()
3789// .with_child(
3790// FlexItem::new(
3791// self.center.render(
3792// &project,
3793// &theme,
3794// &self.follower_states,
3795// self.active_call(),
3796// self.active_pane(),
3797// self.zoomed
3798// .as_ref()
3799// .and_then(|zoomed| zoomed.upgrade(cx))
3800// .as_ref(),
3801// &self.app_state,
3802// cx,
3803// ),
3804// )
3805// .flex(1., true),
3806// )
3807// .with_children(
3808// self.render_dock(DockPosition::Bottom, cx),
3809// )
3810// .flex(1., true),
3811// )
3812// .with_children(self.render_dock(DockPosition::Right, cx))
3813// })
3814// .with_child(Overlay::new(
3815// Stack::new()
3816// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3817// enum ZoomBackground {}
3818// let zoomed = zoomed.upgrade(cx)?;
3819
3820// let mut foreground_style =
3821// theme.workspace.zoomed_pane_foreground;
3822// if let Some(zoomed_dock_position) = self.zoomed_position {
3823// foreground_style =
3824// theme.workspace.zoomed_panel_foreground;
3825// let margin = foreground_style.margin.top;
3826// let border = foreground_style.border.top;
3827
3828// // Only include a margin and border on the opposite side.
3829// foreground_style.margin.top = 0.;
3830// foreground_style.margin.left = 0.;
3831// foreground_style.margin.bottom = 0.;
3832// foreground_style.margin.right = 0.;
3833// foreground_style.border.top = false;
3834// foreground_style.border.left = false;
3835// foreground_style.border.bottom = false;
3836// foreground_style.border.right = false;
3837// match zoomed_dock_position {
3838// DockPosition::Left => {
3839// foreground_style.margin.right = margin;
3840// foreground_style.border.right = border;
3841// }
3842// DockPosition::Right => {
3843// foreground_style.margin.left = margin;
3844// foreground_style.border.left = border;
3845// }
3846// DockPosition::Bottom => {
3847// foreground_style.margin.top = margin;
3848// foreground_style.border.top = border;
3849// }
3850// }
3851// }
3852
3853// Some(
3854// ChildView::new(&zoomed, cx)
3855// .contained()
3856// .with_style(foreground_style)
3857// .aligned()
3858// .contained()
3859// .with_style(theme.workspace.zoomed_background)
3860// .mouse::<ZoomBackground>(0)
3861// .capture_all()
3862// .on_down(
3863// MouseButton::Left,
3864// |_, this: &mut Self, cx| {
3865// this.zoom_out(cx);
3866// },
3867// ),
3868// )
3869// }))
3870// .with_children(self.modal.as_ref().map(|modal| {
3871// // Prevent clicks within the modal from falling
3872// // through to the rest of the workspace.
3873// enum ModalBackground {}
3874// MouseEventHandler::new::<ModalBackground, _>(
3875// 0,
3876// cx,
3877// |_, cx| ChildView::new(modal.view.as_any(), cx),
3878// )
3879// .on_click(MouseButton::Left, |_, _, _| {})
3880// .contained()
3881// .with_style(theme.workspace.modal)
3882// .aligned()
3883// .top()
3884// }))
3885// .with_children(self.render_notifications(&theme.workspace, cx)),
3886// ))
3887// .provide_resize_bounds::<WorkspaceBounds>()
3888// .flex(1.0, true),
3889// )
3890// .with_child(ChildView::new(&self.status_bar, cx))
3891// .contained()
3892// .with_background_color(theme.workspace.background),
3893// )
3894// .with_children(DragAndDrop::render(cx))
3895// .with_children(self.render_disconnected_overlay(cx))
3896// .into_any_named("workspace")
3897// }
3898
3899// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3900// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3901// }
3902// }
3903
3904impl WorkspaceStore {
3905 pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3906 Self {
3907 workspaces: Default::default(),
3908 followers: Default::default(),
3909 _subscriptions: vec![],
3910 // client.add_request_handler(cx.weak_model(), Self::handle_follow),
3911 // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3912 // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3913 // ],
3914 client,
3915 }
3916 }
3917
3918 pub fn update_followers(
3919 &self,
3920 project_id: Option<u64>,
3921 room_id: u64,
3922 update: proto::update_followers::Variant,
3923 cx: &AppContext,
3924 ) -> Option<()> {
3925 let follower_ids: Vec<_> = self
3926 .followers
3927 .iter()
3928 .filter_map(|follower| {
3929 if follower.project_id == project_id || project_id.is_none() {
3930 Some(follower.peer_id.into())
3931 } else {
3932 None
3933 }
3934 })
3935 .collect();
3936 if follower_ids.is_empty() {
3937 return None;
3938 }
3939 self.client
3940 .send(proto::UpdateFollowers {
3941 room_id,
3942 project_id,
3943 follower_ids,
3944 variant: Some(update),
3945 })
3946 .log_err()
3947 }
3948
3949 pub async fn handle_follow(
3950 this: Model<Self>,
3951 envelope: TypedEnvelope<proto::Follow>,
3952 _: Arc<Client>,
3953 mut cx: AsyncAppContext,
3954 ) -> Result<proto::FollowResponse> {
3955 this.update(&mut cx, |this, cx| {
3956 let follower = Follower {
3957 project_id: envelope.payload.project_id,
3958 peer_id: envelope.original_sender_id()?,
3959 };
3960 let mut response = proto::FollowResponse::default();
3961 let active_project = this
3962 .workspaces
3963 .iter()
3964 .next()
3965 .and_then(|workspace| {
3966 workspace
3967 .read_with(cx, |this, cx| this.call_handler.active_project(cx))
3968 .log_err()
3969 })
3970 .flatten();
3971 for workspace in &this.workspaces {
3972 workspace
3973 .update(cx, |workspace, cx| {
3974 let handler_response = workspace.handle_follow(follower.project_id, cx);
3975 if response.views.is_empty() {
3976 response.views = handler_response.views;
3977 } else {
3978 response.views.extend_from_slice(&handler_response.views);
3979 }
3980
3981 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3982 if response.active_view_id.is_none()
3983 || Some(workspace.project.downgrade()) == active_project
3984 {
3985 response.active_view_id = Some(active_view_id);
3986 }
3987 }
3988 })
3989 .ok();
3990 }
3991
3992 if let Err(ix) = this.followers.binary_search(&follower) {
3993 this.followers.insert(ix, follower);
3994 }
3995
3996 Ok(response)
3997 })?
3998 }
3999
4000 async fn handle_unfollow(
4001 model: Model<Self>,
4002 envelope: TypedEnvelope<proto::Unfollow>,
4003 _: Arc<Client>,
4004 mut cx: AsyncAppContext,
4005 ) -> Result<()> {
4006 model.update(&mut cx, |this, _| {
4007 let follower = Follower {
4008 project_id: envelope.payload.project_id,
4009 peer_id: envelope.original_sender_id()?,
4010 };
4011 if let Ok(ix) = this.followers.binary_search(&follower) {
4012 this.followers.remove(ix);
4013 }
4014 Ok(())
4015 })?
4016 }
4017
4018 async fn handle_update_followers(
4019 this: Model<Self>,
4020 envelope: TypedEnvelope<proto::UpdateFollowers>,
4021 _: Arc<Client>,
4022 mut cx: AsyncWindowContext,
4023 ) -> Result<()> {
4024 let leader_id = envelope.original_sender_id()?;
4025 let update = envelope.payload;
4026
4027 this.update(&mut cx, |this, cx| {
4028 for workspace in &this.workspaces {
4029 workspace.update(cx, |workspace, cx| {
4030 let project_id = workspace.project.read(cx).remote_id();
4031 if update.project_id != project_id && update.project_id.is_some() {
4032 return;
4033 }
4034 workspace.handle_update_followers(leader_id, update.clone(), cx);
4035 })?;
4036 }
4037 Ok(())
4038 })?
4039 }
4040}
4041
4042impl ViewId {
4043 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4044 Ok(Self {
4045 creator: message
4046 .creator
4047 .ok_or_else(|| anyhow!("creator is missing"))?,
4048 id: message.id,
4049 })
4050 }
4051
4052 pub(crate) fn to_proto(&self) -> proto::ViewId {
4053 proto::ViewId {
4054 creator: Some(self.creator),
4055 id: self.id,
4056 }
4057 }
4058}
4059
4060pub trait WorkspaceHandle {
4061 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4062}
4063
4064impl WorkspaceHandle for View<Workspace> {
4065 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4066 self.read(cx)
4067 .worktrees(cx)
4068 .flat_map(|worktree| {
4069 let worktree_id = worktree.read(cx).id();
4070 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4071 worktree_id,
4072 path: f.path.clone(),
4073 })
4074 })
4075 .collect::<Vec<_>>()
4076 }
4077}
4078
4079impl std::fmt::Debug for OpenPaths {
4080 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4081 f.debug_struct("OpenPaths")
4082 .field("paths", &self.paths)
4083 .finish()
4084 }
4085}
4086
4087pub struct WorkspaceCreated(pub WeakView<Workspace>);
4088
4089pub fn activate_workspace_for_project(
4090 cx: &mut AppContext,
4091 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4092) -> Option<WindowHandle<Workspace>> {
4093 for window in cx.windows() {
4094 let Some(workspace) = window.downcast::<Workspace>() else {
4095 continue;
4096 };
4097
4098 let predicate = workspace
4099 .update(cx, |workspace, cx| {
4100 let project = workspace.project.read(cx);
4101 if predicate(project, cx) {
4102 cx.activate_window();
4103 true
4104 } else {
4105 false
4106 }
4107 })
4108 .log_err()
4109 .unwrap_or(false);
4110
4111 if predicate {
4112 return Some(workspace);
4113 }
4114 }
4115
4116 None
4117}
4118
4119pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4120 DB.last_workspace().await.log_err().flatten()
4121}
4122
4123// async fn join_channel_internal(
4124// channel_id: u64,
4125// app_state: &Arc<AppState>,
4126// requesting_window: Option<WindowHandle<Workspace>>,
4127// active_call: &ModelHandle<ActiveCall>,
4128// cx: &mut AsyncAppContext,
4129// ) -> Result<bool> {
4130// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4131// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4132// return (false, None);
4133// };
4134
4135// let already_in_channel = room.channel_id() == Some(channel_id);
4136// let should_prompt = room.is_sharing_project()
4137// && room.remote_participants().len() > 0
4138// && !already_in_channel;
4139// let open_room = if already_in_channel {
4140// active_call.room().cloned()
4141// } else {
4142// None
4143// };
4144// (should_prompt, open_room)
4145// });
4146
4147// if let Some(room) = open_room {
4148// let task = room.update(cx, |room, cx| {
4149// if let Some((project, host)) = room.most_active_project(cx) {
4150// return Some(join_remote_project(project, host, app_state.clone(), cx));
4151// }
4152
4153// None
4154// });
4155// if let Some(task) = task {
4156// task.await?;
4157// }
4158// return anyhow::Ok(true);
4159// }
4160
4161// if should_prompt {
4162// if let Some(workspace) = requesting_window {
4163// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4164// let answer = window.prompt(
4165// PromptLevel::Warning,
4166// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4167// &["Yes, Join Channel", "Cancel"],
4168// cx,
4169// );
4170
4171// if let Some(mut answer) = answer {
4172// if answer.next().await == Some(1) {
4173// return Ok(false);
4174// }
4175// }
4176// } else {
4177// return Ok(false); // unreachable!() hopefully
4178// }
4179// } else {
4180// return Ok(false); // unreachable!() hopefully
4181// }
4182// }
4183
4184// let client = cx.read(|cx| active_call.read(cx).client());
4185
4186// let mut client_status = client.status();
4187
4188// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4189// 'outer: loop {
4190// let Some(status) = client_status.recv().await else {
4191// return Err(anyhow!("error connecting"));
4192// };
4193
4194// match status {
4195// Status::Connecting
4196// | Status::Authenticating
4197// | Status::Reconnecting
4198// | Status::Reauthenticating => continue,
4199// Status::Connected { .. } => break 'outer,
4200// Status::SignedOut => return Err(anyhow!("not signed in")),
4201// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4202// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4203// return Err(anyhow!("zed is offline"))
4204// }
4205// }
4206// }
4207
4208// let room = active_call
4209// .update(cx, |active_call, cx| {
4210// active_call.join_channel(channel_id, cx)
4211// })
4212// .await?;
4213
4214// room.update(cx, |room, _| room.room_update_completed())
4215// .await;
4216
4217// let task = room.update(cx, |room, cx| {
4218// if let Some((project, host)) = room.most_active_project(cx) {
4219// return Some(join_remote_project(project, host, app_state.clone(), cx));
4220// }
4221
4222// None
4223// });
4224// if let Some(task) = task {
4225// task.await?;
4226// return anyhow::Ok(true);
4227// }
4228// anyhow::Ok(false)
4229// }
4230
4231// pub fn join_channel(
4232// channel_id: u64,
4233// app_state: Arc<AppState>,
4234// requesting_window: Option<WindowHandle<Workspace>>,
4235// cx: &mut AppContext,
4236// ) -> Task<Result<()>> {
4237// let active_call = ActiveCall::global(cx);
4238// cx.spawn(|mut cx| async move {
4239// let result = join_channel_internal(
4240// channel_id,
4241// &app_state,
4242// requesting_window,
4243// &active_call,
4244// &mut cx,
4245// )
4246// .await;
4247
4248// // join channel succeeded, and opened a window
4249// if matches!(result, Ok(true)) {
4250// return anyhow::Ok(());
4251// }
4252
4253// if requesting_window.is_some() {
4254// return anyhow::Ok(());
4255// }
4256
4257// // find an existing workspace to focus and show call controls
4258// let mut active_window = activate_any_workspace_window(&mut cx);
4259// if active_window.is_none() {
4260// // no open workspaces, make one to show the error in (blergh)
4261// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4262// .await;
4263// }
4264
4265// active_window = activate_any_workspace_window(&mut cx);
4266// if active_window.is_none() {
4267// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4268// }
4269
4270// if let Err(err) = result {
4271// let prompt = active_window.unwrap().prompt(
4272// PromptLevel::Critical,
4273// &format!("Failed to join channel: {}", err),
4274// &["Ok"],
4275// &mut cx,
4276// );
4277// if let Some(mut prompt) = prompt {
4278// prompt.next().await;
4279// } else {
4280// return Err(err);
4281// }
4282// }
4283
4284// // return ok, we showed the error to the user.
4285// return anyhow::Ok(());
4286// })
4287// }
4288
4289// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4290// for window in cx.windows() {
4291// let found = window.update(cx, |cx| {
4292// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4293// if is_workspace {
4294// cx.activate_window();
4295// }
4296// is_workspace
4297// });
4298// if found == Some(true) {
4299// return Some(window);
4300// }
4301// }
4302// None
4303// }
4304
4305#[allow(clippy::type_complexity)]
4306pub fn open_paths(
4307 abs_paths: &[PathBuf],
4308 app_state: &Arc<AppState>,
4309 requesting_window: Option<WindowHandle<Workspace>>,
4310 cx: &mut AppContext,
4311) -> Task<
4312 anyhow::Result<(
4313 WindowHandle<Workspace>,
4314 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4315 )>,
4316> {
4317 let app_state = app_state.clone();
4318 let abs_paths = abs_paths.to_vec();
4319 // Open paths in existing workspace if possible
4320 let existing = activate_workspace_for_project(cx, {
4321 let abs_paths = abs_paths.clone();
4322 move |project, cx| project.contains_paths(&abs_paths, cx)
4323 });
4324 cx.spawn(move |mut cx| async move {
4325 if let Some(existing) = existing {
4326 // // Ok((
4327 // existing.clone(),
4328 // cx.update_window_root(&existing, |workspace, cx| {
4329 // workspace.open_paths(abs_paths, true, cx)
4330 // })?
4331 // .await,
4332 // ))
4333 todo!()
4334 } else {
4335 cx.update(move |cx| {
4336 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4337 })?
4338 .await
4339 }
4340 })
4341}
4342
4343pub fn open_new(
4344 app_state: &Arc<AppState>,
4345 cx: &mut AppContext,
4346 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4347) -> Task<()> {
4348 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4349 cx.spawn(|mut cx| async move {
4350 if let Some((workspace, opened_paths)) = task.await.log_err() {
4351 workspace
4352 .update(&mut cx, |workspace, cx| {
4353 if opened_paths.is_empty() {
4354 init(workspace, cx)
4355 }
4356 })
4357 .log_err();
4358 }
4359 })
4360}
4361
4362pub fn create_and_open_local_file(
4363 path: &'static Path,
4364 cx: &mut ViewContext<Workspace>,
4365 default_content: impl 'static + Send + FnOnce() -> Rope,
4366) -> Task<Result<Box<dyn ItemHandle>>> {
4367 cx.spawn(|workspace, mut cx| async move {
4368 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4369 if !fs.is_file(path).await {
4370 fs.create_file(path, Default::default()).await?;
4371 fs.save(path, &default_content(), Default::default())
4372 .await?;
4373 }
4374
4375 let mut items = workspace
4376 .update(&mut cx, |workspace, cx| {
4377 workspace.with_local_workspace(cx, |workspace, cx| {
4378 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4379 })
4380 })?
4381 .await?
4382 .await;
4383
4384 let item = items.pop().flatten();
4385 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4386 })
4387}
4388
4389// pub fn join_remote_project(
4390// project_id: u64,
4391// follow_user_id: u64,
4392// app_state: Arc<AppState>,
4393// cx: &mut AppContext,
4394// ) -> Task<Result<()>> {
4395// cx.spawn(|mut cx| async move {
4396// let windows = cx.windows();
4397// let existing_workspace = windows.into_iter().find_map(|window| {
4398// window.downcast::<Workspace>().and_then(|window| {
4399// window
4400// .read_root_with(&cx, |workspace, cx| {
4401// if workspace.project().read(cx).remote_id() == Some(project_id) {
4402// Some(cx.handle().downgrade())
4403// } else {
4404// None
4405// }
4406// })
4407// .unwrap_or(None)
4408// })
4409// });
4410
4411// let workspace = if let Some(existing_workspace) = existing_workspace {
4412// existing_workspace
4413// } else {
4414// let active_call = cx.read(ActiveCall::global);
4415// let room = active_call
4416// .read_with(&cx, |call, _| call.room().cloned())
4417// .ok_or_else(|| anyhow!("not in a call"))?;
4418// let project = room
4419// .update(&mut cx, |room, cx| {
4420// room.join_project(
4421// project_id,
4422// app_state.languages.clone(),
4423// app_state.fs.clone(),
4424// cx,
4425// )
4426// })
4427// .await?;
4428
4429// let window_bounds_override = window_bounds_env_override(&cx);
4430// let window = cx.add_window(
4431// (app_state.build_window_options)(
4432// window_bounds_override,
4433// None,
4434// cx.platform().as_ref(),
4435// ),
4436// |cx| Workspace::new(0, project, app_state.clone(), cx),
4437// );
4438// let workspace = window.root(&cx).unwrap();
4439// (app_state.initialize_workspace)(
4440// workspace.downgrade(),
4441// false,
4442// app_state.clone(),
4443// cx.clone(),
4444// )
4445// .await
4446// .log_err();
4447
4448// workspace.downgrade()
4449// };
4450
4451// workspace.window().activate(&mut cx);
4452// cx.platform().activate(true);
4453
4454// workspace.update(&mut cx, |workspace, cx| {
4455// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4456// let follow_peer_id = room
4457// .read(cx)
4458// .remote_participants()
4459// .iter()
4460// .find(|(_, participant)| participant.user.id == follow_user_id)
4461// .map(|(_, p)| p.peer_id)
4462// .or_else(|| {
4463// // If we couldn't follow the given user, follow the host instead.
4464// let collaborator = workspace
4465// .project()
4466// .read(cx)
4467// .collaborators()
4468// .values()
4469// .find(|collaborator| collaborator.replica_id == 0)?;
4470// Some(collaborator.peer_id)
4471// });
4472
4473// if let Some(follow_peer_id) = follow_peer_id {
4474// workspace
4475// .follow(follow_peer_id, cx)
4476// .map(|follow| follow.detach_and_log_err(cx));
4477// }
4478// }
4479// })?;
4480
4481// anyhow::Ok(())
4482// })
4483// }
4484
4485// pub fn restart(_: &Restart, cx: &mut AppContext) {
4486// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4487// cx.spawn(|mut cx| async move {
4488// let mut workspace_windows = cx
4489// .windows()
4490// .into_iter()
4491// .filter_map(|window| window.downcast::<Workspace>())
4492// .collect::<Vec<_>>();
4493
4494// // If multiple windows have unsaved changes, and need a save prompt,
4495// // prompt in the active window before switching to a different window.
4496// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4497
4498// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4499// let answer = window.prompt(
4500// PromptLevel::Info,
4501// "Are you sure you want to restart?",
4502// &["Restart", "Cancel"],
4503// &mut cx,
4504// );
4505
4506// if let Some(mut answer) = answer {
4507// let answer = answer.next().await;
4508// if answer != Some(0) {
4509// return Ok(());
4510// }
4511// }
4512// }
4513
4514// // If the user cancels any save prompt, then keep the app open.
4515// for window in workspace_windows {
4516// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4517// workspace.prepare_to_close(true, cx)
4518// }) {
4519// if !should_close.await? {
4520// return Ok(());
4521// }
4522// }
4523// }
4524// cx.platform().restart();
4525// anyhow::Ok(())
4526// })
4527// .detach_and_log_err(cx);
4528// }
4529
4530fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4531 let mut parts = value.split(',');
4532 let x: usize = parts.next()?.parse().ok()?;
4533 let y: usize = parts.next()?.parse().ok()?;
4534 Some(point((x as f64).into(), (y as f64).into()))
4535}
4536
4537fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4538 let mut parts = value.split(',');
4539 let width: usize = parts.next()?.parse().ok()?;
4540 let height: usize = parts.next()?.parse().ok()?;
4541 Some(size((width as f64).into(), (height as f64).into()))
4542}
4543
4544// #[cfg(test)]
4545// mod tests {
4546// use super::*;
4547// use crate::{
4548// dock::test::TestPanel,
4549// item::test::{TestItem, TestItemEvent, TestProjectItem},
4550// };
4551// use fs::FakeFs;
4552// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4553// use project::{Project, ProjectEntryId};
4554// use serde_json::json;
4555// use settings::SettingsStore;
4556// use std::{cell::RefCell, rc::Rc};
4557
4558// #[gpui::test]
4559// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4560// init_test(cx);
4561
4562// let fs = FakeFs::new(cx.background());
4563// let project = Project::test(fs, [], cx).await;
4564// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4565// let workspace = window.root(cx);
4566
4567// // Adding an item with no ambiguity renders the tab without detail.
4568// let item1 = window.build_view(cx, |_| {
4569// let mut item = TestItem::new();
4570// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4571// item
4572// });
4573// workspace.update(cx, |workspace, cx| {
4574// workspace.add_item(Box::new(item1.clone()), cx);
4575// });
4576// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4577
4578// // Adding an item that creates ambiguity increases the level of detail on
4579// // both tabs.
4580// let item2 = window.build_view(cx, |_| {
4581// let mut item = TestItem::new();
4582// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4583// item
4584// });
4585// workspace.update(cx, |workspace, cx| {
4586// workspace.add_item(Box::new(item2.clone()), cx);
4587// });
4588// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4589// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4590
4591// // Adding an item that creates ambiguity increases the level of detail only
4592// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4593// // we stop at the highest detail available.
4594// let item3 = window.build_view(cx, |_| {
4595// let mut item = TestItem::new();
4596// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4597// item
4598// });
4599// workspace.update(cx, |workspace, cx| {
4600// workspace.add_item(Box::new(item3.clone()), cx);
4601// });
4602// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4603// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4604// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4605// }
4606
4607// #[gpui::test]
4608// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4609// init_test(cx);
4610
4611// let fs = FakeFs::new(cx.background());
4612// fs.insert_tree(
4613// "/root1",
4614// json!({
4615// "one.txt": "",
4616// "two.txt": "",
4617// }),
4618// )
4619// .await;
4620// fs.insert_tree(
4621// "/root2",
4622// json!({
4623// "three.txt": "",
4624// }),
4625// )
4626// .await;
4627
4628// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4629// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4630// let workspace = window.root(cx);
4631// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4632// let worktree_id = project.read_with(cx, |project, cx| {
4633// project.worktrees().next().unwrap().read(cx).id()
4634// });
4635
4636// let item1 = window.build_view(cx, |cx| {
4637// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4638// });
4639// let item2 = window.build_view(cx, |cx| {
4640// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4641// });
4642
4643// // Add an item to an empty pane
4644// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4645// project.read_with(cx, |project, cx| {
4646// assert_eq!(
4647// project.active_entry(),
4648// project
4649// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4650// .map(|e| e.id)
4651// );
4652// });
4653// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4654
4655// // Add a second item to a non-empty pane
4656// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4657// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4658// project.read_with(cx, |project, cx| {
4659// assert_eq!(
4660// project.active_entry(),
4661// project
4662// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4663// .map(|e| e.id)
4664// );
4665// });
4666
4667// // Close the active item
4668// pane.update(cx, |pane, cx| {
4669// pane.close_active_item(&Default::default(), cx).unwrap()
4670// })
4671// .await
4672// .unwrap();
4673// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4674// project.read_with(cx, |project, cx| {
4675// assert_eq!(
4676// project.active_entry(),
4677// project
4678// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4679// .map(|e| e.id)
4680// );
4681// });
4682
4683// // Add a project folder
4684// project
4685// .update(cx, |project, cx| {
4686// project.find_or_create_local_worktree("/root2", true, cx)
4687// })
4688// .await
4689// .unwrap();
4690// assert_eq!(
4691// window.current_title(cx).as_deref(),
4692// Some("one.txt β root1, root2")
4693// );
4694
4695// // Remove a project folder
4696// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4697// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4698// }
4699
4700// #[gpui::test]
4701// async fn test_close_window(cx: &mut TestAppContext) {
4702// init_test(cx);
4703
4704// let fs = FakeFs::new(cx.background());
4705// fs.insert_tree("/root", json!({ "one": "" })).await;
4706
4707// let project = Project::test(fs, ["root".as_ref()], cx).await;
4708// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4709// let workspace = window.root(cx);
4710
4711// // When there are no dirty items, there's nothing to do.
4712// let item1 = window.build_view(cx, |_| TestItem::new());
4713// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4714// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4715// assert!(task.await.unwrap());
4716
4717// // When there are dirty untitled items, prompt to save each one. If the user
4718// // cancels any prompt, then abort.
4719// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4720// let item3 = window.build_view(cx, |cx| {
4721// TestItem::new()
4722// .with_dirty(true)
4723// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4724// });
4725// workspace.update(cx, |w, cx| {
4726// w.add_item(Box::new(item2.clone()), cx);
4727// w.add_item(Box::new(item3.clone()), cx);
4728// });
4729// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4730// cx.foreground().run_until_parked();
4731// window.simulate_prompt_answer(2, cx); // cancel save all
4732// cx.foreground().run_until_parked();
4733// window.simulate_prompt_answer(2, cx); // cancel save all
4734// cx.foreground().run_until_parked();
4735// assert!(!window.has_pending_prompt(cx));
4736// assert!(!task.await.unwrap());
4737// }
4738
4739// #[gpui::test]
4740// async fn test_close_pane_items(cx: &mut TestAppContext) {
4741// init_test(cx);
4742
4743// let fs = FakeFs::new(cx.background());
4744
4745// let project = Project::test(fs, None, cx).await;
4746// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4747// let workspace = window.root(cx);
4748
4749// let item1 = window.build_view(cx, |cx| {
4750// TestItem::new()
4751// .with_dirty(true)
4752// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4753// });
4754// let item2 = window.build_view(cx, |cx| {
4755// TestItem::new()
4756// .with_dirty(true)
4757// .with_conflict(true)
4758// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4759// });
4760// let item3 = window.build_view(cx, |cx| {
4761// TestItem::new()
4762// .with_dirty(true)
4763// .with_conflict(true)
4764// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4765// });
4766// let item4 = window.build_view(cx, |cx| {
4767// TestItem::new()
4768// .with_dirty(true)
4769// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4770// });
4771// let pane = workspace.update(cx, |workspace, cx| {
4772// workspace.add_item(Box::new(item1.clone()), cx);
4773// workspace.add_item(Box::new(item2.clone()), cx);
4774// workspace.add_item(Box::new(item3.clone()), cx);
4775// workspace.add_item(Box::new(item4.clone()), cx);
4776// workspace.active_pane().clone()
4777// });
4778
4779// let close_items = pane.update(cx, |pane, cx| {
4780// pane.activate_item(1, true, true, cx);
4781// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4782// let item1_id = item1.id();
4783// let item3_id = item3.id();
4784// let item4_id = item4.id();
4785// pane.close_items(cx, SaveIntent::Close, move |id| {
4786// [item1_id, item3_id, item4_id].contains(&id)
4787// })
4788// });
4789// cx.foreground().run_until_parked();
4790
4791// assert!(window.has_pending_prompt(cx));
4792// // Ignore "Save all" prompt
4793// window.simulate_prompt_answer(2, cx);
4794// cx.foreground().run_until_parked();
4795// // There's a prompt to save item 1.
4796// pane.read_with(cx, |pane, _| {
4797// assert_eq!(pane.items_len(), 4);
4798// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4799// });
4800// // Confirm saving item 1.
4801// window.simulate_prompt_answer(0, cx);
4802// cx.foreground().run_until_parked();
4803
4804// // Item 1 is saved. There's a prompt to save item 3.
4805// pane.read_with(cx, |pane, cx| {
4806// assert_eq!(item1.read(cx).save_count, 1);
4807// assert_eq!(item1.read(cx).save_as_count, 0);
4808// assert_eq!(item1.read(cx).reload_count, 0);
4809// assert_eq!(pane.items_len(), 3);
4810// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4811// });
4812// assert!(window.has_pending_prompt(cx));
4813
4814// // Cancel saving item 3.
4815// window.simulate_prompt_answer(1, cx);
4816// cx.foreground().run_until_parked();
4817
4818// // Item 3 is reloaded. There's a prompt to save item 4.
4819// pane.read_with(cx, |pane, cx| {
4820// assert_eq!(item3.read(cx).save_count, 0);
4821// assert_eq!(item3.read(cx).save_as_count, 0);
4822// assert_eq!(item3.read(cx).reload_count, 1);
4823// assert_eq!(pane.items_len(), 2);
4824// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4825// });
4826// assert!(window.has_pending_prompt(cx));
4827
4828// // Confirm saving item 4.
4829// window.simulate_prompt_answer(0, cx);
4830// cx.foreground().run_until_parked();
4831
4832// // There's a prompt for a path for item 4.
4833// cx.simulate_new_path_selection(|_| Some(Default::default()));
4834// close_items.await.unwrap();
4835
4836// // The requested items are closed.
4837// pane.read_with(cx, |pane, cx| {
4838// assert_eq!(item4.read(cx).save_count, 0);
4839// assert_eq!(item4.read(cx).save_as_count, 1);
4840// assert_eq!(item4.read(cx).reload_count, 0);
4841// assert_eq!(pane.items_len(), 1);
4842// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4843// });
4844// }
4845
4846// #[gpui::test]
4847// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4848// init_test(cx);
4849
4850// let fs = FakeFs::new(cx.background());
4851
4852// let project = Project::test(fs, [], cx).await;
4853// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4854// let workspace = window.root(cx);
4855
4856// // Create several workspace items with single project entries, and two
4857// // workspace items with multiple project entries.
4858// let single_entry_items = (0..=4)
4859// .map(|project_entry_id| {
4860// window.build_view(cx, |cx| {
4861// TestItem::new()
4862// .with_dirty(true)
4863// .with_project_items(&[TestProjectItem::new(
4864// project_entry_id,
4865// &format!("{project_entry_id}.txt"),
4866// cx,
4867// )])
4868// })
4869// })
4870// .collect::<Vec<_>>();
4871// let item_2_3 = window.build_view(cx, |cx| {
4872// TestItem::new()
4873// .with_dirty(true)
4874// .with_singleton(false)
4875// .with_project_items(&[
4876// single_entry_items[2].read(cx).project_items[0].clone(),
4877// single_entry_items[3].read(cx).project_items[0].clone(),
4878// ])
4879// });
4880// let item_3_4 = window.build_view(cx, |cx| {
4881// TestItem::new()
4882// .with_dirty(true)
4883// .with_singleton(false)
4884// .with_project_items(&[
4885// single_entry_items[3].read(cx).project_items[0].clone(),
4886// single_entry_items[4].read(cx).project_items[0].clone(),
4887// ])
4888// });
4889
4890// // Create two panes that contain the following project entries:
4891// // left pane:
4892// // multi-entry items: (2, 3)
4893// // single-entry items: 0, 1, 2, 3, 4
4894// // right pane:
4895// // single-entry items: 1
4896// // multi-entry items: (3, 4)
4897// let left_pane = workspace.update(cx, |workspace, cx| {
4898// let left_pane = workspace.active_pane().clone();
4899// workspace.add_item(Box::new(item_2_3.clone()), cx);
4900// for item in single_entry_items {
4901// workspace.add_item(Box::new(item), cx);
4902// }
4903// left_pane.update(cx, |pane, cx| {
4904// pane.activate_item(2, true, true, cx);
4905// });
4906
4907// workspace
4908// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4909// .unwrap();
4910
4911// left_pane
4912// });
4913
4914// //Need to cause an effect flush in order to respect new focus
4915// workspace.update(cx, |workspace, cx| {
4916// workspace.add_item(Box::new(item_3_4.clone()), cx);
4917// cx.focus(&left_pane);
4918// });
4919
4920// // When closing all of the items in the left pane, we should be prompted twice:
4921// // once for project entry 0, and once for project entry 2. After those two
4922// // prompts, the task should complete.
4923
4924// let close = left_pane.update(cx, |pane, cx| {
4925// pane.close_items(cx, SaveIntent::Close, move |_| true)
4926// });
4927// cx.foreground().run_until_parked();
4928// // Discard "Save all" prompt
4929// window.simulate_prompt_answer(2, cx);
4930
4931// cx.foreground().run_until_parked();
4932// left_pane.read_with(cx, |pane, cx| {
4933// assert_eq!(
4934// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4935// &[ProjectEntryId::from_proto(0)]
4936// );
4937// });
4938// window.simulate_prompt_answer(0, cx);
4939
4940// cx.foreground().run_until_parked();
4941// left_pane.read_with(cx, |pane, cx| {
4942// assert_eq!(
4943// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4944// &[ProjectEntryId::from_proto(2)]
4945// );
4946// });
4947// window.simulate_prompt_answer(0, cx);
4948
4949// cx.foreground().run_until_parked();
4950// close.await.unwrap();
4951// left_pane.read_with(cx, |pane, _| {
4952// assert_eq!(pane.items_len(), 0);
4953// });
4954// }
4955
4956// #[gpui::test]
4957// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4958// init_test(cx);
4959
4960// let fs = FakeFs::new(cx.background());
4961
4962// let project = Project::test(fs, [], cx).await;
4963// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4964// let workspace = window.root(cx);
4965// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4966
4967// let item = window.build_view(cx, |cx| {
4968// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4969// });
4970// let item_id = item.id();
4971// workspace.update(cx, |workspace, cx| {
4972// workspace.add_item(Box::new(item.clone()), cx);
4973// });
4974
4975// // Autosave on window change.
4976// item.update(cx, |item, cx| {
4977// cx.update_global(|settings: &mut SettingsStore, cx| {
4978// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4979// settings.autosave = Some(AutosaveSetting::OnWindowChange);
4980// })
4981// });
4982// item.is_dirty = true;
4983// });
4984
4985// // Deactivating the window saves the file.
4986// window.simulate_deactivation(cx);
4987// deterministic.run_until_parked();
4988// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4989
4990// // Autosave on focus change.
4991// item.update(cx, |item, cx| {
4992// cx.focus_self();
4993// cx.update_global(|settings: &mut SettingsStore, cx| {
4994// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4995// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4996// })
4997// });
4998// item.is_dirty = true;
4999// });
5000
5001// // Blurring the item saves the file.
5002// item.update(cx, |_, cx| cx.blur());
5003// deterministic.run_until_parked();
5004// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
5005
5006// // Deactivating the window still saves the file.
5007// window.simulate_activation(cx);
5008// item.update(cx, |item, cx| {
5009// cx.focus_self();
5010// item.is_dirty = true;
5011// });
5012// window.simulate_deactivation(cx);
5013
5014// deterministic.run_until_parked();
5015// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5016
5017// // Autosave after delay.
5018// item.update(cx, |item, cx| {
5019// cx.update_global(|settings: &mut SettingsStore, cx| {
5020// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5021// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5022// })
5023// });
5024// item.is_dirty = true;
5025// cx.emit(TestItemEvent::Edit);
5026// });
5027
5028// // Delay hasn't fully expired, so the file is still dirty and unsaved.
5029// deterministic.advance_clock(Duration::from_millis(250));
5030// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5031
5032// // After delay expires, the file is saved.
5033// deterministic.advance_clock(Duration::from_millis(250));
5034// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5035
5036// // Autosave on focus change, ensuring closing the tab counts as such.
5037// item.update(cx, |item, cx| {
5038// cx.update_global(|settings: &mut SettingsStore, cx| {
5039// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5040// settings.autosave = Some(AutosaveSetting::OnFocusChange);
5041// })
5042// });
5043// item.is_dirty = true;
5044// });
5045
5046// pane.update(cx, |pane, cx| {
5047// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5048// })
5049// .await
5050// .unwrap();
5051// assert!(!window.has_pending_prompt(cx));
5052// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5053
5054// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5055// workspace.update(cx, |workspace, cx| {
5056// workspace.add_item(Box::new(item.clone()), cx);
5057// });
5058// item.update(cx, |item, cx| {
5059// item.project_items[0].update(cx, |item, _| {
5060// item.entry_id = None;
5061// });
5062// item.is_dirty = true;
5063// cx.blur();
5064// });
5065// deterministic.run_until_parked();
5066// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5067
5068// // Ensure autosave is prevented for deleted files also when closing the buffer.
5069// let _close_items = pane.update(cx, |pane, cx| {
5070// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5071// });
5072// deterministic.run_until_parked();
5073// assert!(window.has_pending_prompt(cx));
5074// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5075// }
5076
5077// #[gpui::test]
5078// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5079// init_test(cx);
5080
5081// let fs = FakeFs::new(cx.background());
5082
5083// let project = Project::test(fs, [], cx).await;
5084// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5085// let workspace = window.root(cx);
5086
5087// let item = window.build_view(cx, |cx| {
5088// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5089// });
5090// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5091// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5092// let toolbar_notify_count = Rc::new(RefCell::new(0));
5093
5094// workspace.update(cx, |workspace, cx| {
5095// workspace.add_item(Box::new(item.clone()), cx);
5096// let toolbar_notification_count = toolbar_notify_count.clone();
5097// cx.observe(&toolbar, move |_, _, _| {
5098// *toolbar_notification_count.borrow_mut() += 1
5099// })
5100// .detach();
5101// });
5102
5103// pane.read_with(cx, |pane, _| {
5104// assert!(!pane.can_navigate_backward());
5105// assert!(!pane.can_navigate_forward());
5106// });
5107
5108// item.update(cx, |item, cx| {
5109// item.set_state("one".to_string(), cx);
5110// });
5111
5112// // Toolbar must be notified to re-render the navigation buttons
5113// assert_eq!(*toolbar_notify_count.borrow(), 1);
5114
5115// pane.read_with(cx, |pane, _| {
5116// assert!(pane.can_navigate_backward());
5117// assert!(!pane.can_navigate_forward());
5118// });
5119
5120// workspace
5121// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5122// .await
5123// .unwrap();
5124
5125// assert_eq!(*toolbar_notify_count.borrow(), 3);
5126// pane.read_with(cx, |pane, _| {
5127// assert!(!pane.can_navigate_backward());
5128// assert!(pane.can_navigate_forward());
5129// });
5130// }
5131
5132// #[gpui::test]
5133// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5134// init_test(cx);
5135// let fs = FakeFs::new(cx.background());
5136
5137// let project = Project::test(fs, [], cx).await;
5138// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5139// let workspace = window.root(cx);
5140
5141// let panel = workspace.update(cx, |workspace, cx| {
5142// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5143// workspace.add_panel(panel.clone(), cx);
5144
5145// workspace
5146// .right_dock()
5147// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5148
5149// panel
5150// });
5151
5152// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5153// pane.update(cx, |pane, cx| {
5154// let item = cx.build_view(|_| TestItem::new());
5155// pane.add_item(Box::new(item), true, true, None, cx);
5156// });
5157
5158// // Transfer focus from center to panel
5159// workspace.update(cx, |workspace, cx| {
5160// workspace.toggle_panel_focus::<TestPanel>(cx);
5161// });
5162
5163// workspace.read_with(cx, |workspace, cx| {
5164// assert!(workspace.right_dock().read(cx).is_open());
5165// assert!(!panel.is_zoomed(cx));
5166// assert!(panel.has_focus(cx));
5167// });
5168
5169// // Transfer focus from panel to center
5170// workspace.update(cx, |workspace, cx| {
5171// workspace.toggle_panel_focus::<TestPanel>(cx);
5172// });
5173
5174// workspace.read_with(cx, |workspace, cx| {
5175// assert!(workspace.right_dock().read(cx).is_open());
5176// assert!(!panel.is_zoomed(cx));
5177// assert!(!panel.has_focus(cx));
5178// });
5179
5180// // Close the dock
5181// workspace.update(cx, |workspace, cx| {
5182// workspace.toggle_dock(DockPosition::Right, cx);
5183// });
5184
5185// workspace.read_with(cx, |workspace, cx| {
5186// assert!(!workspace.right_dock().read(cx).is_open());
5187// assert!(!panel.is_zoomed(cx));
5188// assert!(!panel.has_focus(cx));
5189// });
5190
5191// // Open the dock
5192// workspace.update(cx, |workspace, cx| {
5193// workspace.toggle_dock(DockPosition::Right, cx);
5194// });
5195
5196// workspace.read_with(cx, |workspace, cx| {
5197// assert!(workspace.right_dock().read(cx).is_open());
5198// assert!(!panel.is_zoomed(cx));
5199// assert!(panel.has_focus(cx));
5200// });
5201
5202// // Focus and zoom panel
5203// panel.update(cx, |panel, cx| {
5204// cx.focus_self();
5205// panel.set_zoomed(true, cx)
5206// });
5207
5208// workspace.read_with(cx, |workspace, cx| {
5209// assert!(workspace.right_dock().read(cx).is_open());
5210// assert!(panel.is_zoomed(cx));
5211// assert!(panel.has_focus(cx));
5212// });
5213
5214// // Transfer focus to the center closes the dock
5215// workspace.update(cx, |workspace, cx| {
5216// workspace.toggle_panel_focus::<TestPanel>(cx);
5217// });
5218
5219// workspace.read_with(cx, |workspace, cx| {
5220// assert!(!workspace.right_dock().read(cx).is_open());
5221// assert!(panel.is_zoomed(cx));
5222// assert!(!panel.has_focus(cx));
5223// });
5224
5225// // Transferring focus back to the panel keeps it zoomed
5226// workspace.update(cx, |workspace, cx| {
5227// workspace.toggle_panel_focus::<TestPanel>(cx);
5228// });
5229
5230// workspace.read_with(cx, |workspace, cx| {
5231// assert!(workspace.right_dock().read(cx).is_open());
5232// assert!(panel.is_zoomed(cx));
5233// assert!(panel.has_focus(cx));
5234// });
5235
5236// // Close the dock while it is zoomed
5237// workspace.update(cx, |workspace, cx| {
5238// workspace.toggle_dock(DockPosition::Right, cx)
5239// });
5240
5241// workspace.read_with(cx, |workspace, cx| {
5242// assert!(!workspace.right_dock().read(cx).is_open());
5243// assert!(panel.is_zoomed(cx));
5244// assert!(workspace.zoomed.is_none());
5245// assert!(!panel.has_focus(cx));
5246// });
5247
5248// // Opening the dock, when it's zoomed, retains focus
5249// workspace.update(cx, |workspace, cx| {
5250// workspace.toggle_dock(DockPosition::Right, cx)
5251// });
5252
5253// workspace.read_with(cx, |workspace, cx| {
5254// assert!(workspace.right_dock().read(cx).is_open());
5255// assert!(panel.is_zoomed(cx));
5256// assert!(workspace.zoomed.is_some());
5257// assert!(panel.has_focus(cx));
5258// });
5259
5260// // Unzoom and close the panel, zoom the active pane.
5261// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5262// workspace.update(cx, |workspace, cx| {
5263// workspace.toggle_dock(DockPosition::Right, cx)
5264// });
5265// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5266
5267// // Opening a dock unzooms the pane.
5268// workspace.update(cx, |workspace, cx| {
5269// workspace.toggle_dock(DockPosition::Right, cx)
5270// });
5271// workspace.read_with(cx, |workspace, cx| {
5272// let pane = pane.read(cx);
5273// assert!(!pane.is_zoomed());
5274// assert!(!pane.has_focus());
5275// assert!(workspace.right_dock().read(cx).is_open());
5276// assert!(workspace.zoomed.is_none());
5277// });
5278// }
5279
5280// #[gpui::test]
5281// async fn test_panels(cx: &mut gpui::TestAppContext) {
5282// init_test(cx);
5283// let fs = FakeFs::new(cx.background());
5284
5285// let project = Project::test(fs, [], cx).await;
5286// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5287// let workspace = window.root(cx);
5288
5289// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5290// // Add panel_1 on the left, panel_2 on the right.
5291// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5292// workspace.add_panel(panel_1.clone(), cx);
5293// workspace
5294// .left_dock()
5295// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5296// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5297// workspace.add_panel(panel_2.clone(), cx);
5298// workspace
5299// .right_dock()
5300// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5301
5302// let left_dock = workspace.left_dock();
5303// assert_eq!(
5304// left_dock.read(cx).visible_panel().unwrap().id(),
5305// panel_1.id()
5306// );
5307// assert_eq!(
5308// left_dock.read(cx).active_panel_size(cx).unwrap(),
5309// panel_1.size(cx)
5310// );
5311
5312// left_dock.update(cx, |left_dock, cx| {
5313// left_dock.resize_active_panel(Some(1337.), cx)
5314// });
5315// assert_eq!(
5316// workspace
5317// .right_dock()
5318// .read(cx)
5319// .visible_panel()
5320// .unwrap()
5321// .id(),
5322// panel_2.id()
5323// );
5324
5325// (panel_1, panel_2)
5326// });
5327
5328// // Move panel_1 to the right
5329// panel_1.update(cx, |panel_1, cx| {
5330// panel_1.set_position(DockPosition::Right, cx)
5331// });
5332
5333// workspace.update(cx, |workspace, cx| {
5334// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5335// // Since it was the only panel on the left, the left dock should now be closed.
5336// assert!(!workspace.left_dock().read(cx).is_open());
5337// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5338// let right_dock = workspace.right_dock();
5339// assert_eq!(
5340// right_dock.read(cx).visible_panel().unwrap().id(),
5341// panel_1.id()
5342// );
5343// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5344
5345// // Now we move panel_2Β to the left
5346// panel_2.set_position(DockPosition::Left, cx);
5347// });
5348
5349// workspace.update(cx, |workspace, cx| {
5350// // Since panel_2 was not visible on the right, we don't open the left dock.
5351// assert!(!workspace.left_dock().read(cx).is_open());
5352// // And the right dock is unaffected in it's displaying of panel_1
5353// assert!(workspace.right_dock().read(cx).is_open());
5354// assert_eq!(
5355// workspace
5356// .right_dock()
5357// .read(cx)
5358// .visible_panel()
5359// .unwrap()
5360// .id(),
5361// panel_1.id()
5362// );
5363// });
5364
5365// // Move panel_1 back to the left
5366// panel_1.update(cx, |panel_1, cx| {
5367// panel_1.set_position(DockPosition::Left, cx)
5368// });
5369
5370// workspace.update(cx, |workspace, cx| {
5371// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5372// let left_dock = workspace.left_dock();
5373// assert!(left_dock.read(cx).is_open());
5374// assert_eq!(
5375// left_dock.read(cx).visible_panel().unwrap().id(),
5376// panel_1.id()
5377// );
5378// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5379// // And right the dock should be closed as it no longer has any panels.
5380// assert!(!workspace.right_dock().read(cx).is_open());
5381
5382// // Now we move panel_1 to the bottom
5383// panel_1.set_position(DockPosition::Bottom, cx);
5384// });
5385
5386// workspace.update(cx, |workspace, cx| {
5387// // Since panel_1 was visible on the left, we close the left dock.
5388// assert!(!workspace.left_dock().read(cx).is_open());
5389// // The bottom dock is sized based on the panel's default size,
5390// // since the panel orientation changed from vertical to horizontal.
5391// let bottom_dock = workspace.bottom_dock();
5392// assert_eq!(
5393// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5394// panel_1.size(cx),
5395// );
5396// // Close bottom dock and move panel_1 back to the left.
5397// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5398// panel_1.set_position(DockPosition::Left, cx);
5399// });
5400
5401// // Emit activated event on panel 1
5402// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5403
5404// // Now the left dock is open and panel_1 is active and focused.
5405// workspace.read_with(cx, |workspace, cx| {
5406// let left_dock = workspace.left_dock();
5407// assert!(left_dock.read(cx).is_open());
5408// assert_eq!(
5409// left_dock.read(cx).visible_panel().unwrap().id(),
5410// panel_1.id()
5411// );
5412// assert!(panel_1.is_focused(cx));
5413// });
5414
5415// // Emit closed event on panel 2, which is not active
5416// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5417
5418// // Wo don't close the left dock, because panel_2 wasn't the active panel
5419// workspace.read_with(cx, |workspace, cx| {
5420// let left_dock = workspace.left_dock();
5421// assert!(left_dock.read(cx).is_open());
5422// assert_eq!(
5423// left_dock.read(cx).visible_panel().unwrap().id(),
5424// panel_1.id()
5425// );
5426// });
5427
5428// // Emitting a ZoomIn event shows the panel as zoomed.
5429// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5430// workspace.read_with(cx, |workspace, _| {
5431// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5432// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5433// });
5434
5435// // Move panel to another dock while it is zoomed
5436// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5437// workspace.read_with(cx, |workspace, _| {
5438// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5439// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5440// });
5441
5442// // If focus is transferred to another view that's not a panel or another pane, we still show
5443// // the panel as zoomed.
5444// let focus_receiver = window.build_view(cx, |_| EmptyView);
5445// focus_receiver.update(cx, |_, cx| cx.focus_self());
5446// workspace.read_with(cx, |workspace, _| {
5447// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5448// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5449// });
5450
5451// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5452// workspace.update(cx, |_, cx| cx.focus_self());
5453// workspace.read_with(cx, |workspace, _| {
5454// assert_eq!(workspace.zoomed, None);
5455// assert_eq!(workspace.zoomed_position, None);
5456// });
5457
5458// // If focus is transferred again to another view that's not a panel or a pane, we won't
5459// // show the panel as zoomed because it wasn't zoomed before.
5460// focus_receiver.update(cx, |_, cx| cx.focus_self());
5461// workspace.read_with(cx, |workspace, _| {
5462// assert_eq!(workspace.zoomed, None);
5463// assert_eq!(workspace.zoomed_position, None);
5464// });
5465
5466// // When focus is transferred back to the panel, it is zoomed again.
5467// panel_1.update(cx, |_, cx| cx.focus_self());
5468// workspace.read_with(cx, |workspace, _| {
5469// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5470// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5471// });
5472
5473// // Emitting a ZoomOut event unzooms the panel.
5474// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5475// workspace.read_with(cx, |workspace, _| {
5476// assert_eq!(workspace.zoomed, None);
5477// assert_eq!(workspace.zoomed_position, None);
5478// });
5479
5480// // Emit closed event on panel 1, which is active
5481// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5482
5483// // Now the left dock is closed, because panel_1 was the active panel
5484// workspace.read_with(cx, |workspace, cx| {
5485// let right_dock = workspace.right_dock();
5486// assert!(!right_dock.read(cx).is_open());
5487// });
5488// }
5489
5490// pub fn init_test(cx: &mut TestAppContext) {
5491// cx.foreground().forbid_parking();
5492// cx.update(|cx| {
5493// cx.set_global(SettingsStore::test(cx));
5494// theme::init((), cx);
5495// language::init(cx);
5496// crate::init_settings(cx);
5497// Project::init_settings(cx);
5498// });
5499// }
5500// }