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