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