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