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