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, register_action, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
33 AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
34 FocusHandle, FocusableView, GlobalPixels, InteractiveComponent, KeyContext, 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::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
67
68pub use ui;
69use util::ResultExt;
70use uuid::Uuid;
71pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
72
73use crate::persistence::model::{
74 DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
75};
76
77lazy_static! {
78 static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
79 .ok()
80 .as_deref()
81 .and_then(parse_pixel_size_env_var);
82 static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
83 .ok()
84 .as_deref()
85 .and_then(parse_pixel_position_env_var);
86}
87
88// #[derive(Clone, PartialEq)]
89// pub struct RemoveWorktreeFromProject(pub WorktreeId);
90
91actions!(
92 Open,
93 NewFile,
94 NewWindow,
95 CloseWindow,
96 CloseInactiveTabsAndPanes,
97 AddFolderToProject,
98 Unfollow,
99 SaveAs,
100 ReloadActiveItem,
101 ActivatePreviousPane,
102 ActivateNextPane,
103 FollowNextCollaborator,
104 NewTerminal,
105 NewCenterTerminal,
106 ToggleTerminalFocus,
107 NewSearch,
108 Feedback,
109 Restart,
110 Welcome,
111 ToggleZoom,
112 ToggleLeftDock,
113 ToggleRightDock,
114 ToggleBottomDock,
115 CloseAllDocks,
116);
117
118// #[derive(Clone, PartialEq)]
119// pub struct OpenPaths {
120// pub paths: Vec<PathBuf>,
121// }
122
123// #[derive(Clone, Deserialize, PartialEq)]
124// pub struct ActivatePane(pub usize);
125
126// #[derive(Clone, Deserialize, PartialEq)]
127// pub struct ActivatePaneInDirection(pub SplitDirection);
128
129// #[derive(Clone, Deserialize, PartialEq)]
130// pub struct SwapPaneInDirection(pub SplitDirection);
131
132// #[derive(Clone, Deserialize, PartialEq)]
133// pub struct NewFileInDirection(pub SplitDirection);
134
135// #[derive(Clone, PartialEq, Debug, Deserialize)]
136// #[serde(rename_all = "camelCase")]
137// pub struct SaveAll {
138// pub save_intent: Option<SaveIntent>,
139// }
140
141// #[derive(Clone, PartialEq, Debug, Deserialize)]
142// #[serde(rename_all = "camelCase")]
143// pub struct Save {
144// pub save_intent: Option<SaveIntent>,
145// }
146
147// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
148// #[serde(rename_all = "camelCase")]
149// pub struct CloseAllItemsAndPanes {
150// pub save_intent: Option<SaveIntent>,
151// }
152
153#[derive(Deserialize)]
154pub struct Toast {
155 id: usize,
156 msg: Cow<'static, str>,
157 #[serde(skip)]
158 on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
159}
160
161impl Toast {
162 pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
163 Toast {
164 id,
165 msg: msg.into(),
166 on_click: None,
167 }
168 }
169
170 pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
171 where
172 M: Into<Cow<'static, str>>,
173 F: Fn(&mut WindowContext) + 'static,
174 {
175 self.on_click = Some((message.into(), Arc::new(on_click)));
176 self
177 }
178}
179
180impl PartialEq for Toast {
181 fn eq(&self, other: &Self) -> bool {
182 self.id == other.id
183 && self.msg == other.msg
184 && self.on_click.is_some() == other.on_click.is_some()
185 }
186}
187
188impl Clone for Toast {
189 fn clone(&self) -> Self {
190 Toast {
191 id: self.id,
192 msg: self.msg.to_owned(),
193 on_click: self.on_click.clone(),
194 }
195 }
196}
197
198#[register_action]
199#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
200pub struct OpenTerminal {
201 pub working_directory: PathBuf,
202}
203
204// impl_actions!(
205// workspace,
206// [
207// ActivatePane,
208// ActivatePaneInDirection,
209// SwapPaneInDirection,
210// NewFileInDirection,
211// Toast,
212// SaveAll,
213// Save,
214// CloseAllItemsAndPanes,
215// ]
216// );
217
218pub type WorkspaceId = i64;
219
220pub fn init_settings(cx: &mut AppContext) {
221 WorkspaceSettings::register(cx);
222 ItemSettings::register(cx);
223}
224
225pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
226 init_settings(cx);
227 pane::init(cx);
228 notifications::init(cx);
229
230 // cx.add_global_action({
231 // let app_state = Arc::downgrade(&app_state);
232 // move |_: &Open, cx: &mut AppContext| {
233 // let mut paths = cx.prompt_for_paths(PathPromptOptions {
234 // files: true,
235 // directories: true,
236 // multiple: true,
237 // });
238
239 // if let Some(app_state) = app_state.upgrade() {
240 // cx.spawn(move |mut cx| async move {
241 // if let Some(paths) = paths.recv().await.flatten() {
242 // cx.update(|cx| {
243 // open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
244 // });
245 // }
246 // })
247 // .detach();
248 // }
249 // }
250 // });
251}
252
253type ProjectItemBuilders =
254 HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
255pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
256 let builders = cx.default_global::<ProjectItemBuilders>();
257 builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
258 let item = model.downcast::<I::Item>().unwrap();
259 Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
260 });
261}
262
263type FollowableItemBuilder = fn(
264 View<Pane>,
265 View<Workspace>,
266 ViewId,
267 &mut Option<proto::view::Variant>,
268 &mut AppContext,
269) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
270type FollowableItemBuilders = HashMap<
271 TypeId,
272 (
273 FollowableItemBuilder,
274 fn(&AnyView) -> Box<dyn FollowableItemHandle>,
275 ),
276>;
277pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
278 let builders = cx.default_global::<FollowableItemBuilders>();
279 builders.insert(
280 TypeId::of::<I>(),
281 (
282 |pane, workspace, id, state, cx| {
283 I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
284 cx.foreground_executor()
285 .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
286 })
287 },
288 |this| Box::new(this.clone().downcast::<I>().unwrap()),
289 ),
290 );
291}
292
293type ItemDeserializers = HashMap<
294 Arc<str>,
295 fn(
296 Model<Project>,
297 WeakView<Workspace>,
298 WorkspaceId,
299 ItemId,
300 &mut ViewContext<Pane>,
301 ) -> Task<Result<Box<dyn ItemHandle>>>,
302>;
303pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
304 if let Some(serialized_item_kind) = I::serialized_item_kind() {
305 let deserializers = cx.default_global::<ItemDeserializers>();
306 deserializers.insert(
307 Arc::from(serialized_item_kind),
308 |project, workspace, workspace_id, item_id, cx| {
309 let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
310 cx.foreground_executor()
311 .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
312 },
313 );
314 }
315}
316
317pub struct AppState {
318 pub languages: Arc<LanguageRegistry>,
319 pub client: Arc<Client>,
320 pub user_store: Model<UserStore>,
321 pub workspace_store: Model<WorkspaceStore>,
322 pub fs: Arc<dyn fs2::Fs>,
323 pub build_window_options:
324 fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
325 pub node_runtime: Arc<dyn NodeRuntime>,
326}
327
328pub struct WorkspaceStore {
329 workspaces: HashSet<WindowHandle<Workspace>>,
330 followers: Vec<Follower>,
331 client: Arc<Client>,
332 _subscriptions: Vec<client2::Subscription>,
333}
334
335#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
336struct Follower {
337 project_id: Option<u64>,
338 peer_id: PeerId,
339}
340
341impl AppState {
342 #[cfg(any(test, feature = "test-support"))]
343 pub fn test(cx: &mut AppContext) -> Arc<Self> {
344 use node_runtime::FakeNodeRuntime;
345 use settings2::SettingsStore;
346
347 if !cx.has_global::<SettingsStore>() {
348 let settings_store = SettingsStore::test(cx);
349 cx.set_global(settings_store);
350 }
351
352 let fs = fs2::FakeFs::new(cx.background_executor().clone());
353 let languages = Arc::new(LanguageRegistry::test());
354 let http_client = util::http::FakeHttpClient::with_404_response();
355 let client = Client::new(http_client.clone(), cx);
356 let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
357 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
358
359 theme2::init(cx);
360 client2::init(&client, cx);
361 crate::init_settings(cx);
362
363 Arc::new(Self {
364 client,
365 fs,
366 languages,
367 user_store,
368 workspace_store,
369 node_runtime: FakeNodeRuntime::new(),
370 build_window_options: |_, _, _| Default::default(),
371 })
372 }
373}
374
375struct DelayedDebouncedEditAction {
376 task: Option<Task<()>>,
377 cancel_channel: Option<oneshot::Sender<()>>,
378}
379
380impl DelayedDebouncedEditAction {
381 fn new() -> DelayedDebouncedEditAction {
382 DelayedDebouncedEditAction {
383 task: None,
384 cancel_channel: None,
385 }
386 }
387
388 fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
389 where
390 F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
391 {
392 if let Some(channel) = self.cancel_channel.take() {
393 _ = channel.send(());
394 }
395
396 let (sender, mut receiver) = oneshot::channel::<()>();
397 self.cancel_channel = Some(sender);
398
399 let previous_task = self.task.take();
400 self.task = Some(cx.spawn(move |workspace, mut cx| async move {
401 let mut timer = cx.background_executor().timer(delay).fuse();
402 if let Some(previous_task) = previous_task {
403 previous_task.await;
404 }
405
406 futures::select_biased! {
407 _ = receiver => return,
408 _ = timer => {}
409 }
410
411 if let Some(result) = workspace
412 .update(&mut cx, |workspace, cx| (func)(workspace, cx))
413 .log_err()
414 {
415 result.await.log_err();
416 }
417 }));
418 }
419}
420
421pub enum Event {
422 PaneAdded(View<Pane>),
423 ContactRequestedJoin(u64),
424 WorkspaceCreated(WeakView<Workspace>),
425}
426
427pub struct Workspace {
428 weak_self: WeakView<Self>,
429 workspace_actions: Vec<Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>>,
430 zoomed: Option<AnyWeakView>,
431 zoomed_position: Option<DockPosition>,
432 center: PaneGroup,
433 left_dock: View<Dock>,
434 bottom_dock: View<Dock>,
435 right_dock: View<Dock>,
436 panes: Vec<View<Pane>>,
437 panes_by_item: HashMap<EntityId, WeakView<Pane>>,
438 active_pane: View<Pane>,
439 last_active_center_pane: Option<WeakView<Pane>>,
440 last_active_view_id: Option<proto::ViewId>,
441 status_bar: View<StatusBar>,
442 modal_layer: View<ModalLayer>,
443 titlebar_item: Option<AnyView>,
444 notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
445 project: Model<Project>,
446 follower_states: HashMap<View<Pane>, FollowerState>,
447 last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
448 window_edited: bool,
449 active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
450 leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
451 database_id: WorkspaceId,
452 app_state: Arc<AppState>,
453 subscriptions: Vec<Subscription>,
454 _apply_leader_updates: Task<Result<()>>,
455 _observe_current_user: Task<Result<()>>,
456 _schedule_serialize: Option<Task<()>>,
457 pane_history_timestamp: Arc<AtomicUsize>,
458}
459
460#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
461pub struct ViewId {
462 pub creator: PeerId,
463 pub id: u64,
464}
465
466#[derive(Default)]
467struct FollowerState {
468 leader_id: PeerId,
469 active_view_id: Option<ViewId>,
470 items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
471}
472
473enum WorkspaceBounds {}
474
475impl Workspace {
476 pub fn new(
477 workspace_id: WorkspaceId,
478 project: Model<Project>,
479 app_state: Arc<AppState>,
480 cx: &mut ViewContext<Self>,
481 ) -> Self {
482 cx.observe(&project, |_, _, cx| cx.notify()).detach();
483 cx.subscribe(&project, move |this, _, event, cx| {
484 match event {
485 project2::Event::RemoteIdChanged(_) => {
486 this.update_window_title(cx);
487 }
488
489 project2::Event::CollaboratorLeft(peer_id) => {
490 this.collaborator_left(*peer_id, cx);
491 }
492
493 project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
494 this.update_window_title(cx);
495 this.serialize_workspace(cx);
496 }
497
498 project2::Event::DisconnectedFromHost => {
499 this.update_window_edited(cx);
500 cx.blur();
501 }
502
503 project2::Event::Closed => {
504 cx.remove_window();
505 }
506
507 project2::Event::DeletedEntry(entry_id) => {
508 for pane in this.panes.iter() {
509 pane.update(cx, |pane, cx| {
510 pane.handle_deleted_project_item(*entry_id, cx)
511 });
512 }
513 }
514
515 project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
516 cx.build_view(|_| MessageNotification::new(message.clone()))
517 }),
518
519 _ => {}
520 }
521 cx.notify()
522 })
523 .detach();
524
525 let weak_handle = cx.view().downgrade();
526 let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
527
528 let center_pane = cx.build_view(|cx| {
529 Pane::new(
530 weak_handle.clone(),
531 project.clone(),
532 pane_history_timestamp.clone(),
533 cx,
534 )
535 });
536 cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
537 // todo!()
538 // cx.focus(¢er_pane);
539 cx.emit(Event::PaneAdded(center_pane.clone()));
540
541 let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
542 app_state.workspace_store.update(cx, |store, _| {
543 store.workspaces.insert(window_handle);
544 });
545
546 let mut current_user = app_state.user_store.read(cx).watch_current_user();
547 let mut connection_status = app_state.client.status();
548 let _observe_current_user = cx.spawn(|this, mut cx| async move {
549 current_user.next().await;
550 connection_status.next().await;
551 let mut stream =
552 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
553
554 while stream.recv().await.is_some() {
555 this.update(&mut cx, |_, cx| cx.notify())?;
556 }
557 anyhow::Ok(())
558 });
559
560 // All leader updates are enqueued and then processed in a single task, so
561 // that each asynchronous operation can be run in order.
562 let (leader_updates_tx, mut leader_updates_rx) =
563 mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
564 let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
565 while let Some((leader_id, update)) = leader_updates_rx.next().await {
566 Self::process_leader_update(&this, leader_id, update, &mut cx)
567 .await
568 .log_err();
569 }
570
571 Ok(())
572 });
573
574 cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
575
576 let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
577 let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
578 let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
579 let left_dock_buttons =
580 cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
581 let bottom_dock_buttons =
582 cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
583 let right_dock_buttons =
584 cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
585 let status_bar = cx.build_view(|cx| {
586 let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
587 status_bar.add_left_item(left_dock_buttons, cx);
588 status_bar.add_right_item(right_dock_buttons, cx);
589 status_bar.add_right_item(bottom_dock_buttons, cx);
590 status_bar
591 });
592
593 let workspace_handle = cx.view().downgrade();
594 let modal_layer = cx.build_view(|cx| ModalLayer::new());
595
596 // todo!()
597 // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
598 // drag_and_drop.register_container(weak_handle.clone());
599 // });
600
601 let mut active_call = None;
602 if cx.has_global::<Model<ActiveCall>>() {
603 let call = cx.global::<Model<ActiveCall>>().clone();
604 let mut subscriptions = Vec::new();
605 subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
606 active_call = Some((call, subscriptions));
607 }
608
609 let subscriptions = vec![
610 cx.observe_window_activation(Self::on_window_activation_changed),
611 cx.observe_window_bounds(move |_, cx| {
612 if let Some(display) = cx.display() {
613 // Transform fixed bounds to be stored in terms of the containing display
614 let mut bounds = cx.window_bounds();
615 if let WindowBounds::Fixed(window_bounds) = &mut bounds {
616 let display_bounds = display.bounds();
617 window_bounds.origin.x -= display_bounds.origin.x;
618 window_bounds.origin.y -= display_bounds.origin.y;
619 }
620
621 if let Some(display_uuid) = display.uuid().log_err() {
622 cx.background_executor()
623 .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
624 .detach_and_log_err(cx);
625 }
626 }
627 cx.notify();
628 }),
629 cx.observe(&left_dock, |this, _, cx| {
630 this.serialize_workspace(cx);
631 cx.notify();
632 }),
633 cx.observe(&bottom_dock, |this, _, cx| {
634 this.serialize_workspace(cx);
635 cx.notify();
636 }),
637 cx.observe(&right_dock, |this, _, cx| {
638 this.serialize_workspace(cx);
639 cx.notify();
640 }),
641 ];
642
643 cx.defer(|this, cx| this.update_window_title(cx));
644 Workspace {
645 weak_self: weak_handle.clone(),
646 zoomed: None,
647 zoomed_position: None,
648 center: PaneGroup::new(center_pane.clone()),
649 panes: vec![center_pane.clone()],
650 panes_by_item: Default::default(),
651 active_pane: center_pane.clone(),
652 last_active_center_pane: Some(center_pane.downgrade()),
653 last_active_view_id: None,
654 status_bar,
655 modal_layer,
656 titlebar_item: None,
657 notifications: Default::default(),
658 left_dock,
659 bottom_dock,
660 right_dock,
661 project: project.clone(),
662 follower_states: Default::default(),
663 last_leaders_by_pane: Default::default(),
664 window_edited: false,
665 active_call,
666 database_id: workspace_id,
667 app_state,
668 _observe_current_user,
669 _apply_leader_updates,
670 _schedule_serialize: None,
671 leader_updates_tx,
672 subscriptions,
673 pane_history_timestamp,
674 workspace_actions: Default::default(),
675 }
676 }
677
678 fn new_local(
679 abs_paths: Vec<PathBuf>,
680 app_state: Arc<AppState>,
681 requesting_window: Option<WindowHandle<Workspace>>,
682 cx: &mut AppContext,
683 ) -> Task<
684 anyhow::Result<(
685 WindowHandle<Workspace>,
686 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
687 )>,
688 > {
689 let project_handle = Project::local(
690 app_state.client.clone(),
691 app_state.node_runtime.clone(),
692 app_state.user_store.clone(),
693 app_state.languages.clone(),
694 app_state.fs.clone(),
695 cx,
696 );
697
698 cx.spawn(|mut cx| async move {
699 let serialized_workspace: Option<SerializedWorkspace> =
700 persistence::DB.workspace_for_roots(&abs_paths.as_slice());
701
702 let paths_to_open = Arc::new(abs_paths);
703
704 // Get project paths for all of the abs_paths
705 let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
706 let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
707 Vec::with_capacity(paths_to_open.len());
708 for path in paths_to_open.iter().cloned() {
709 if let Some((worktree, project_entry)) = cx
710 .update(|cx| {
711 Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
712 })?
713 .await
714 .log_err()
715 {
716 worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
717 project_paths.push((path, Some(project_entry)));
718 } else {
719 project_paths.push((path, None));
720 }
721 }
722
723 let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
724 serialized_workspace.id
725 } else {
726 DB.next_id().await.unwrap_or(0)
727 };
728
729 let window = if let Some(window) = requesting_window {
730 cx.update_window(window.into(), |old_workspace, cx| {
731 cx.replace_root_view(|cx| {
732 Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
733 });
734 })?;
735 window
736 } else {
737 let window_bounds_override = window_bounds_env_override(&cx);
738 let (bounds, display) = if let Some(bounds) = window_bounds_override {
739 (Some(bounds), None)
740 } else {
741 serialized_workspace
742 .as_ref()
743 .and_then(|serialized_workspace| {
744 let serialized_display = serialized_workspace.display?;
745 let mut bounds = serialized_workspace.bounds?;
746
747 // Stored bounds are relative to the containing display.
748 // So convert back to global coordinates if that screen still exists
749 if let WindowBounds::Fixed(mut window_bounds) = bounds {
750 let screen = cx
751 .update(|cx| {
752 cx.displays().into_iter().find(|display| {
753 display.uuid().ok() == Some(serialized_display)
754 })
755 })
756 .ok()??;
757 let screen_bounds = screen.bounds();
758 window_bounds.origin.x += screen_bounds.origin.x;
759 window_bounds.origin.y += screen_bounds.origin.y;
760 bounds = WindowBounds::Fixed(window_bounds);
761 }
762
763 Some((bounds, serialized_display))
764 })
765 .unzip()
766 };
767
768 // Use the serialized workspace to construct the new window
769 let options =
770 cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
771
772 cx.open_window(options, {
773 let app_state = app_state.clone();
774 let workspace_id = workspace_id.clone();
775 let project_handle = project_handle.clone();
776 move |cx| {
777 cx.build_view(|cx| {
778 Workspace::new(workspace_id, project_handle, app_state, cx)
779 })
780 }
781 })?
782 };
783
784 // todo!() Ask how to do this
785 // let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
786 // let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?;
787
788 // (app_state.initialize_workspace)(
789 // weak_view,
790 // serialized_workspace.is_some(),
791 // app_state.clone(),
792 // async_cx,
793 // )
794 // .await
795 // .log_err();
796
797 window
798 .update(&mut cx, |_, cx| cx.activate_window())
799 .log_err();
800
801 notify_if_database_failed(window, &mut cx);
802 let opened_items = window
803 .update(&mut cx, |_workspace, cx| {
804 open_items(serialized_workspace, project_paths, app_state, cx)
805 })?
806 .await
807 .unwrap_or_default();
808
809 Ok((window, opened_items))
810 })
811 }
812
813 pub fn weak_handle(&self) -> WeakView<Self> {
814 self.weak_self.clone()
815 }
816
817 pub fn left_dock(&self) -> &View<Dock> {
818 &self.left_dock
819 }
820
821 pub fn bottom_dock(&self) -> &View<Dock> {
822 &self.bottom_dock
823 }
824
825 pub fn right_dock(&self) -> &View<Dock> {
826 &self.right_dock
827 }
828
829 pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
830 let dock = match panel.position(cx) {
831 DockPosition::Left => &self.left_dock,
832 DockPosition::Bottom => &self.bottom_dock,
833 DockPosition::Right => &self.right_dock,
834 };
835
836 dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
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(div: Div<Self>) -> Div<Self> {
3218 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 // cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3268 // workspace.toggle_dock(DockPosition::Right, cx);
3269 // });
3270 // cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3271 // workspace.toggle_dock(DockPosition::Bottom, cx);
3272 // });
3273 // cx.add_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: Modal + 'static>(&mut self, cx: &ViewContext<Self>) -> Option<View<V>> {
3386 self.modal_layer.read(cx).active_modal()
3387 }
3388
3389 pub fn toggle_modal<V: Modal, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3390 where
3391 B: FnOnce(&mut ViewContext<V>) -> V,
3392 {
3393 self.modal_layer
3394 .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3395 }
3396}
3397
3398fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3399 let display_origin = cx
3400 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3401 .ok()??;
3402 ZED_WINDOW_POSITION
3403 .zip(*ZED_WINDOW_SIZE)
3404 .map(|(position, size)| {
3405 WindowBounds::Fixed(Bounds {
3406 origin: display_origin + position,
3407 size,
3408 })
3409 })
3410}
3411
3412fn open_items(
3413 serialized_workspace: Option<SerializedWorkspace>,
3414 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3415 app_state: Arc<AppState>,
3416 cx: &mut ViewContext<Workspace>,
3417) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3418 let restored_items = serialized_workspace.map(|serialized_workspace| {
3419 Workspace::load_workspace(
3420 serialized_workspace,
3421 project_paths_to_open
3422 .iter()
3423 .map(|(_, project_path)| project_path)
3424 .cloned()
3425 .collect(),
3426 cx,
3427 )
3428 });
3429
3430 cx.spawn(|workspace, mut cx| async move {
3431 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3432
3433 if let Some(restored_items) = restored_items {
3434 let restored_items = restored_items.await?;
3435
3436 let restored_project_paths = restored_items
3437 .iter()
3438 .filter_map(|item| {
3439 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3440 .ok()
3441 .flatten()
3442 })
3443 .collect::<HashSet<_>>();
3444
3445 for restored_item in restored_items {
3446 opened_items.push(restored_item.map(Ok));
3447 }
3448
3449 project_paths_to_open
3450 .iter_mut()
3451 .for_each(|(_, project_path)| {
3452 if let Some(project_path_to_open) = project_path {
3453 if restored_project_paths.contains(project_path_to_open) {
3454 *project_path = None;
3455 }
3456 }
3457 });
3458 } else {
3459 for _ in 0..project_paths_to_open.len() {
3460 opened_items.push(None);
3461 }
3462 }
3463 assert!(opened_items.len() == project_paths_to_open.len());
3464
3465 let tasks =
3466 project_paths_to_open
3467 .into_iter()
3468 .enumerate()
3469 .map(|(i, (abs_path, project_path))| {
3470 let workspace = workspace.clone();
3471 cx.spawn(|mut cx| {
3472 let fs = app_state.fs.clone();
3473 async move {
3474 let file_project_path = project_path?;
3475 if fs.is_file(&abs_path).await {
3476 Some((
3477 i,
3478 workspace
3479 .update(&mut cx, |workspace, cx| {
3480 workspace.open_path(file_project_path, None, true, cx)
3481 })
3482 .log_err()?
3483 .await,
3484 ))
3485 } else {
3486 None
3487 }
3488 }
3489 })
3490 });
3491
3492 let tasks = tasks.collect::<Vec<_>>();
3493
3494 let tasks = futures::future::join_all(tasks.into_iter());
3495 for maybe_opened_path in tasks.await.into_iter() {
3496 if let Some((i, path_open_result)) = maybe_opened_path {
3497 opened_items[i] = Some(path_open_result);
3498 }
3499 }
3500
3501 Ok(opened_items)
3502 })
3503}
3504
3505// todo!()
3506// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3507// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3508// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3509// const MESSAGE_ID: usize = 2;
3510
3511// if workspace
3512// .read_with(cx, |workspace, cx| {
3513// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3514// })
3515// .unwrap_or(false)
3516// {
3517// return;
3518// }
3519
3520// if db::kvp::KEY_VALUE_STORE
3521// .read_kvp(NEW_DOCK_HINT_KEY)
3522// .ok()
3523// .flatten()
3524// .is_some()
3525// {
3526// if !workspace
3527// .read_with(cx, |workspace, cx| {
3528// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3529// })
3530// .unwrap_or(false)
3531// {
3532// cx.update(|cx| {
3533// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3534// let entry = tracker
3535// .entry(TypeId::of::<MessageNotification>())
3536// .or_default();
3537// if !entry.contains(&MESSAGE_ID) {
3538// entry.push(MESSAGE_ID);
3539// }
3540// });
3541// });
3542// }
3543
3544// return;
3545// }
3546
3547// cx.spawn(|_| async move {
3548// db::kvp::KEY_VALUE_STORE
3549// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3550// .await
3551// .ok();
3552// })
3553// .detach();
3554
3555// workspace
3556// .update(cx, |workspace, cx| {
3557// workspace.show_notification_once(2, cx, |cx| {
3558// cx.build_view(|_| {
3559// MessageNotification::new_element(|text, _| {
3560// Text::new(
3561// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3562// text,
3563// )
3564// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3565// let code_span_background_color = settings::get::<ThemeSettings>(cx)
3566// .theme
3567// .editor
3568// .document_highlight_read_background;
3569
3570// cx.scene().push_quad(gpui::Quad {
3571// bounds,
3572// background: Some(code_span_background_color),
3573// border: Default::default(),
3574// corner_radii: (2.0).into(),
3575// })
3576// })
3577// .into_any()
3578// })
3579// .with_click_message("Read more about the new panel system")
3580// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3581// })
3582// })
3583// })
3584// .ok();
3585
3586fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3587 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3588
3589 workspace
3590 .update(cx, |workspace, cx| {
3591 if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3592 workspace.show_notification_once(0, cx, |cx| {
3593 cx.build_view(|_| {
3594 MessageNotification::new("Failed to load the database file.")
3595 .with_click_message("Click to let us know about this error")
3596 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3597 })
3598 });
3599 }
3600 })
3601 .log_err();
3602}
3603
3604impl EventEmitter<Event> for Workspace {}
3605
3606impl FocusableView for Workspace {
3607 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3608 self.active_pane.focus_handle(cx)
3609 }
3610}
3611
3612impl Render for Workspace {
3613 type Element = Div<Self>;
3614
3615 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3616 let mut context = KeyContext::default();
3617 context.add("Workspace");
3618 let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
3619
3620 self.add_workspace_actions_listeners(div())
3621 .key_context(context)
3622 .relative()
3623 .size_full()
3624 .flex()
3625 .flex_col()
3626 .font(ui_font)
3627 .gap_0()
3628 .justify_start()
3629 .items_start()
3630 .text_color(cx.theme().colors().text)
3631 .bg(cx.theme().colors().background)
3632 .children(self.titlebar_item.clone())
3633 .child(
3634 // todo! should this be a component a view?
3635 div()
3636 .id("workspace")
3637 .relative()
3638 .flex_1()
3639 .w_full()
3640 .flex()
3641 .overflow_hidden()
3642 .border_t()
3643 .border_b()
3644 .border_color(cx.theme().colors().border)
3645 .child(self.modal_layer.clone())
3646 .child(
3647 div()
3648 .flex()
3649 .flex_row()
3650 .flex_1()
3651 .h_full()
3652 // Left Dock
3653 .child(
3654 div()
3655 .flex()
3656 .flex_none()
3657 .overflow_hidden()
3658 .child(self.left_dock.clone()),
3659 )
3660 // Panes
3661 .child(
3662 div()
3663 .flex()
3664 .flex_col()
3665 .flex_1()
3666 .child(self.center.render(
3667 &self.project,
3668 &self.follower_states,
3669 self.active_call(),
3670 &self.active_pane,
3671 self.zoomed.as_ref(),
3672 &self.app_state,
3673 cx,
3674 ))
3675 .child(div().flex().flex_1().child(self.bottom_dock.clone())),
3676 )
3677 // Right Dock
3678 .child(
3679 div()
3680 .flex()
3681 .flex_none()
3682 .overflow_hidden()
3683 .child(self.right_dock.clone()),
3684 ),
3685 ),
3686 )
3687 .child(self.status_bar.clone())
3688 // .when(self.debug.show_toast, |this| {
3689 // this.child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
3690 // })
3691 // .children(
3692 // Some(
3693 // div()
3694 // .absolute()
3695 // .top(px(50.))
3696 // .left(px(640.))
3697 // .z_index(8)
3698 // .child(LanguageSelector::new("language-selector")),
3699 // )
3700 // .filter(|_| self.is_language_selector_open()),
3701 // )
3702 .z_index(8)
3703 // Debug
3704 .child(
3705 div()
3706 .flex()
3707 .flex_col()
3708 .z_index(9)
3709 .absolute()
3710 .top_20()
3711 .left_1_4()
3712 .w_40()
3713 .gap_2(), // .when(self.show_debug, |this| {
3714 // this.child(Button::<Workspace>::new("Toggle User Settings").on_click(
3715 // Arc::new(|workspace, cx| workspace.debug_toggle_user_settings(cx)),
3716 // ))
3717 // .child(
3718 // Button::<Workspace>::new("Toggle Toasts").on_click(Arc::new(
3719 // |workspace, cx| workspace.debug_toggle_toast(cx),
3720 // )),
3721 // )
3722 // .child(
3723 // Button::<Workspace>::new("Toggle Livestream").on_click(Arc::new(
3724 // |workspace, cx| workspace.debug_toggle_livestream(cx),
3725 // )),
3726 // )
3727 // })
3728 // .child(
3729 // Button::<Workspace>::new("Toggle Debug")
3730 // .on_click(Arc::new(|workspace, cx| workspace.toggle_debug(cx))),
3731 // ),
3732 )
3733 }
3734}
3735// todo!()
3736// impl Entity for Workspace {
3737// type Event = Event;
3738
3739// fn release(&mut self, cx: &mut AppContext) {
3740// self.app_state.workspace_store.update(cx, |store, _| {
3741// store.workspaces.remove(&self.weak_self);
3742// })
3743// }
3744// }
3745
3746// impl View for Workspace {
3747// fn ui_name() -> &'static str {
3748// "Workspace"
3749// }
3750
3751// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3752// let theme = theme::current(cx).clone();
3753// Stack::new()
3754// .with_child(
3755// Flex::column()
3756// .with_child(self.render_titlebar(&theme, cx))
3757// .with_child(
3758// Stack::new()
3759// .with_child({
3760// let project = self.project.clone();
3761// Flex::row()
3762// .with_children(self.render_dock(DockPosition::Left, cx))
3763// .with_child(
3764// Flex::column()
3765// .with_child(
3766// FlexItem::new(
3767// self.center.render(
3768// &project,
3769// &theme,
3770// &self.follower_states,
3771// self.active_call(),
3772// self.active_pane(),
3773// self.zoomed
3774// .as_ref()
3775// .and_then(|zoomed| zoomed.upgrade(cx))
3776// .as_ref(),
3777// &self.app_state,
3778// cx,
3779// ),
3780// )
3781// .flex(1., true),
3782// )
3783// .with_children(
3784// self.render_dock(DockPosition::Bottom, cx),
3785// )
3786// .flex(1., true),
3787// )
3788// .with_children(self.render_dock(DockPosition::Right, cx))
3789// })
3790// .with_child(Overlay::new(
3791// Stack::new()
3792// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3793// enum ZoomBackground {}
3794// let zoomed = zoomed.upgrade(cx)?;
3795
3796// let mut foreground_style =
3797// theme.workspace.zoomed_pane_foreground;
3798// if let Some(zoomed_dock_position) = self.zoomed_position {
3799// foreground_style =
3800// theme.workspace.zoomed_panel_foreground;
3801// let margin = foreground_style.margin.top;
3802// let border = foreground_style.border.top;
3803
3804// // Only include a margin and border on the opposite side.
3805// foreground_style.margin.top = 0.;
3806// foreground_style.margin.left = 0.;
3807// foreground_style.margin.bottom = 0.;
3808// foreground_style.margin.right = 0.;
3809// foreground_style.border.top = false;
3810// foreground_style.border.left = false;
3811// foreground_style.border.bottom = false;
3812// foreground_style.border.right = false;
3813// match zoomed_dock_position {
3814// DockPosition::Left => {
3815// foreground_style.margin.right = margin;
3816// foreground_style.border.right = border;
3817// }
3818// DockPosition::Right => {
3819// foreground_style.margin.left = margin;
3820// foreground_style.border.left = border;
3821// }
3822// DockPosition::Bottom => {
3823// foreground_style.margin.top = margin;
3824// foreground_style.border.top = border;
3825// }
3826// }
3827// }
3828
3829// Some(
3830// ChildView::new(&zoomed, cx)
3831// .contained()
3832// .with_style(foreground_style)
3833// .aligned()
3834// .contained()
3835// .with_style(theme.workspace.zoomed_background)
3836// .mouse::<ZoomBackground>(0)
3837// .capture_all()
3838// .on_down(
3839// MouseButton::Left,
3840// |_, this: &mut Self, cx| {
3841// this.zoom_out(cx);
3842// },
3843// ),
3844// )
3845// }))
3846// .with_children(self.modal.as_ref().map(|modal| {
3847// // Prevent clicks within the modal from falling
3848// // through to the rest of the workspace.
3849// enum ModalBackground {}
3850// MouseEventHandler::new::<ModalBackground, _>(
3851// 0,
3852// cx,
3853// |_, cx| ChildView::new(modal.view.as_any(), cx),
3854// )
3855// .on_click(MouseButton::Left, |_, _, _| {})
3856// .contained()
3857// .with_style(theme.workspace.modal)
3858// .aligned()
3859// .top()
3860// }))
3861// .with_children(self.render_notifications(&theme.workspace, cx)),
3862// ))
3863// .provide_resize_bounds::<WorkspaceBounds>()
3864// .flex(1.0, true),
3865// )
3866// .with_child(ChildView::new(&self.status_bar, cx))
3867// .contained()
3868// .with_background_color(theme.workspace.background),
3869// )
3870// .with_children(DragAndDrop::render(cx))
3871// .with_children(self.render_disconnected_overlay(cx))
3872// .into_any_named("workspace")
3873// }
3874
3875// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3876// if cx.is_self_focused() {
3877// cx.focus(&self.active_pane);
3878// }
3879// }
3880
3881// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3882// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3883// }
3884// }
3885
3886impl WorkspaceStore {
3887 pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3888 Self {
3889 workspaces: Default::default(),
3890 followers: Default::default(),
3891 _subscriptions: vec![],
3892 // client.add_request_handler(cx.weak_model(), Self::handle_follow),
3893 // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3894 // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3895 // ],
3896 client,
3897 }
3898 }
3899
3900 pub fn update_followers(
3901 &self,
3902 project_id: Option<u64>,
3903 update: proto::update_followers::Variant,
3904 cx: &AppContext,
3905 ) -> Option<()> {
3906 if !cx.has_global::<Model<ActiveCall>>() {
3907 return None;
3908 }
3909
3910 let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3911 let follower_ids: Vec<_> = self
3912 .followers
3913 .iter()
3914 .filter_map(|follower| {
3915 if follower.project_id == project_id || project_id.is_none() {
3916 Some(follower.peer_id.into())
3917 } else {
3918 None
3919 }
3920 })
3921 .collect();
3922 if follower_ids.is_empty() {
3923 return None;
3924 }
3925 self.client
3926 .send(proto::UpdateFollowers {
3927 room_id,
3928 project_id,
3929 follower_ids,
3930 variant: Some(update),
3931 })
3932 .log_err()
3933 }
3934
3935 pub async fn handle_follow(
3936 this: Model<Self>,
3937 envelope: TypedEnvelope<proto::Follow>,
3938 _: Arc<Client>,
3939 mut cx: AsyncAppContext,
3940 ) -> Result<proto::FollowResponse> {
3941 this.update(&mut cx, |this, cx| {
3942 let follower = Follower {
3943 project_id: envelope.payload.project_id,
3944 peer_id: envelope.original_sender_id()?,
3945 };
3946 let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3947
3948 let mut response = proto::FollowResponse::default();
3949 for workspace in &this.workspaces {
3950 workspace
3951 .update(cx, |workspace, cx| {
3952 let handler_response = workspace.handle_follow(follower.project_id, cx);
3953 if response.views.is_empty() {
3954 response.views = handler_response.views;
3955 } else {
3956 response.views.extend_from_slice(&handler_response.views);
3957 }
3958
3959 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3960 if response.active_view_id.is_none()
3961 || Some(workspace.project.downgrade()) == active_project
3962 {
3963 response.active_view_id = Some(active_view_id);
3964 }
3965 }
3966 })
3967 .ok();
3968 }
3969
3970 if let Err(ix) = this.followers.binary_search(&follower) {
3971 this.followers.insert(ix, follower);
3972 }
3973
3974 Ok(response)
3975 })?
3976 }
3977
3978 async fn handle_unfollow(
3979 model: Model<Self>,
3980 envelope: TypedEnvelope<proto::Unfollow>,
3981 _: Arc<Client>,
3982 mut cx: AsyncAppContext,
3983 ) -> Result<()> {
3984 model.update(&mut cx, |this, _| {
3985 let follower = Follower {
3986 project_id: envelope.payload.project_id,
3987 peer_id: envelope.original_sender_id()?,
3988 };
3989 if let Ok(ix) = this.followers.binary_search(&follower) {
3990 this.followers.remove(ix);
3991 }
3992 Ok(())
3993 })?
3994 }
3995
3996 async fn handle_update_followers(
3997 this: Model<Self>,
3998 envelope: TypedEnvelope<proto::UpdateFollowers>,
3999 _: Arc<Client>,
4000 mut cx: AsyncWindowContext,
4001 ) -> Result<()> {
4002 let leader_id = envelope.original_sender_id()?;
4003 let update = envelope.payload;
4004
4005 this.update(&mut cx, |this, cx| {
4006 for workspace in &this.workspaces {
4007 workspace.update(cx, |workspace, cx| {
4008 let project_id = workspace.project.read(cx).remote_id();
4009 if update.project_id != project_id && update.project_id.is_some() {
4010 return;
4011 }
4012 workspace.handle_update_followers(leader_id, update.clone(), cx);
4013 })?;
4014 }
4015 Ok(())
4016 })?
4017 }
4018}
4019
4020impl ViewId {
4021 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
4022 Ok(Self {
4023 creator: message
4024 .creator
4025 .ok_or_else(|| anyhow!("creator is missing"))?,
4026 id: message.id,
4027 })
4028 }
4029
4030 pub(crate) fn to_proto(&self) -> proto::ViewId {
4031 proto::ViewId {
4032 creator: Some(self.creator),
4033 id: self.id,
4034 }
4035 }
4036}
4037
4038pub trait WorkspaceHandle {
4039 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
4040}
4041
4042impl WorkspaceHandle for View<Workspace> {
4043 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
4044 self.read(cx)
4045 .worktrees(cx)
4046 .flat_map(|worktree| {
4047 let worktree_id = worktree.read(cx).id();
4048 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
4049 worktree_id,
4050 path: f.path.clone(),
4051 })
4052 })
4053 .collect::<Vec<_>>()
4054 }
4055}
4056
4057// impl std::fmt::Debug for OpenPaths {
4058// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4059// f.debug_struct("OpenPaths")
4060// .field("paths", &self.paths)
4061// .finish()
4062// }
4063// }
4064
4065pub struct WorkspaceCreated(pub WeakView<Workspace>);
4066
4067pub fn activate_workspace_for_project(
4068 cx: &mut AppContext,
4069 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4070) -> Option<WindowHandle<Workspace>> {
4071 for window in cx.windows() {
4072 let Some(workspace) = window.downcast::<Workspace>() else {
4073 continue;
4074 };
4075
4076 let predicate = workspace
4077 .update(cx, |workspace, cx| {
4078 let project = workspace.project.read(cx);
4079 if predicate(project, cx) {
4080 cx.activate_window();
4081 true
4082 } else {
4083 false
4084 }
4085 })
4086 .log_err()
4087 .unwrap_or(false);
4088
4089 if predicate {
4090 return Some(workspace);
4091 }
4092 }
4093
4094 None
4095}
4096
4097pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4098 DB.last_workspace().await.log_err().flatten()
4099}
4100
4101// async fn join_channel_internal(
4102// channel_id: u64,
4103// app_state: &Arc<AppState>,
4104// requesting_window: Option<WindowHandle<Workspace>>,
4105// active_call: &ModelHandle<ActiveCall>,
4106// cx: &mut AsyncAppContext,
4107// ) -> Result<bool> {
4108// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4109// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4110// return (false, None);
4111// };
4112
4113// let already_in_channel = room.channel_id() == Some(channel_id);
4114// let should_prompt = room.is_sharing_project()
4115// && room.remote_participants().len() > 0
4116// && !already_in_channel;
4117// let open_room = if already_in_channel {
4118// active_call.room().cloned()
4119// } else {
4120// None
4121// };
4122// (should_prompt, open_room)
4123// });
4124
4125// if let Some(room) = open_room {
4126// let task = room.update(cx, |room, cx| {
4127// if let Some((project, host)) = room.most_active_project(cx) {
4128// return Some(join_remote_project(project, host, app_state.clone(), cx));
4129// }
4130
4131// None
4132// });
4133// if let Some(task) = task {
4134// task.await?;
4135// }
4136// return anyhow::Ok(true);
4137// }
4138
4139// if should_prompt {
4140// if let Some(workspace) = requesting_window {
4141// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4142// let answer = window.prompt(
4143// PromptLevel::Warning,
4144// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4145// &["Yes, Join Channel", "Cancel"],
4146// cx,
4147// );
4148
4149// if let Some(mut answer) = answer {
4150// if answer.next().await == Some(1) {
4151// return Ok(false);
4152// }
4153// }
4154// } else {
4155// return Ok(false); // unreachable!() hopefully
4156// }
4157// } else {
4158// return Ok(false); // unreachable!() hopefully
4159// }
4160// }
4161
4162// let client = cx.read(|cx| active_call.read(cx).client());
4163
4164// let mut client_status = client.status();
4165
4166// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4167// 'outer: loop {
4168// let Some(status) = client_status.recv().await else {
4169// return Err(anyhow!("error connecting"));
4170// };
4171
4172// match status {
4173// Status::Connecting
4174// | Status::Authenticating
4175// | Status::Reconnecting
4176// | Status::Reauthenticating => continue,
4177// Status::Connected { .. } => break 'outer,
4178// Status::SignedOut => return Err(anyhow!("not signed in")),
4179// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4180// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4181// return Err(anyhow!("zed is offline"))
4182// }
4183// }
4184// }
4185
4186// let room = active_call
4187// .update(cx, |active_call, cx| {
4188// active_call.join_channel(channel_id, cx)
4189// })
4190// .await?;
4191
4192// room.update(cx, |room, _| room.room_update_completed())
4193// .await;
4194
4195// let task = room.update(cx, |room, cx| {
4196// if let Some((project, host)) = room.most_active_project(cx) {
4197// return Some(join_remote_project(project, host, app_state.clone(), cx));
4198// }
4199
4200// None
4201// });
4202// if let Some(task) = task {
4203// task.await?;
4204// return anyhow::Ok(true);
4205// }
4206// anyhow::Ok(false)
4207// }
4208
4209// pub fn join_channel(
4210// channel_id: u64,
4211// app_state: Arc<AppState>,
4212// requesting_window: Option<WindowHandle<Workspace>>,
4213// cx: &mut AppContext,
4214// ) -> Task<Result<()>> {
4215// let active_call = ActiveCall::global(cx);
4216// cx.spawn(|mut cx| async move {
4217// let result = join_channel_internal(
4218// channel_id,
4219// &app_state,
4220// requesting_window,
4221// &active_call,
4222// &mut cx,
4223// )
4224// .await;
4225
4226// // join channel succeeded, and opened a window
4227// if matches!(result, Ok(true)) {
4228// return anyhow::Ok(());
4229// }
4230
4231// if requesting_window.is_some() {
4232// return anyhow::Ok(());
4233// }
4234
4235// // find an existing workspace to focus and show call controls
4236// let mut active_window = activate_any_workspace_window(&mut cx);
4237// if active_window.is_none() {
4238// // no open workspaces, make one to show the error in (blergh)
4239// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4240// .await;
4241// }
4242
4243// active_window = activate_any_workspace_window(&mut cx);
4244// if active_window.is_none() {
4245// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4246// }
4247
4248// if let Err(err) = result {
4249// let prompt = active_window.unwrap().prompt(
4250// PromptLevel::Critical,
4251// &format!("Failed to join channel: {}", err),
4252// &["Ok"],
4253// &mut cx,
4254// );
4255// if let Some(mut prompt) = prompt {
4256// prompt.next().await;
4257// } else {
4258// return Err(err);
4259// }
4260// }
4261
4262// // return ok, we showed the error to the user.
4263// return anyhow::Ok(());
4264// })
4265// }
4266
4267// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4268// for window in cx.windows() {
4269// let found = window.update(cx, |cx| {
4270// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4271// if is_workspace {
4272// cx.activate_window();
4273// }
4274// is_workspace
4275// });
4276// if found == Some(true) {
4277// return Some(window);
4278// }
4279// }
4280// None
4281// }
4282
4283#[allow(clippy::type_complexity)]
4284pub fn open_paths(
4285 abs_paths: &[PathBuf],
4286 app_state: &Arc<AppState>,
4287 requesting_window: Option<WindowHandle<Workspace>>,
4288 cx: &mut AppContext,
4289) -> Task<
4290 anyhow::Result<(
4291 WindowHandle<Workspace>,
4292 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4293 )>,
4294> {
4295 let app_state = app_state.clone();
4296 let abs_paths = abs_paths.to_vec();
4297 // Open paths in existing workspace if possible
4298 let existing = activate_workspace_for_project(cx, {
4299 let abs_paths = abs_paths.clone();
4300 move |project, cx| project.contains_paths(&abs_paths, cx)
4301 });
4302 cx.spawn(move |mut cx| async move {
4303 if let Some(existing) = existing {
4304 // // Ok((
4305 // existing.clone(),
4306 // cx.update_window_root(&existing, |workspace, cx| {
4307 // workspace.open_paths(abs_paths, true, cx)
4308 // })?
4309 // .await,
4310 // ))
4311 todo!()
4312 } else {
4313 cx.update(move |cx| {
4314 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4315 })?
4316 .await
4317 }
4318 })
4319}
4320
4321pub fn open_new(
4322 app_state: &Arc<AppState>,
4323 cx: &mut AppContext,
4324 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4325) -> Task<()> {
4326 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4327 cx.spawn(|mut cx| async move {
4328 if let Some((workspace, opened_paths)) = task.await.log_err() {
4329 workspace
4330 .update(&mut cx, |workspace, cx| {
4331 if opened_paths.is_empty() {
4332 init(workspace, cx)
4333 }
4334 })
4335 .log_err();
4336 }
4337 })
4338}
4339
4340pub fn create_and_open_local_file(
4341 path: &'static Path,
4342 cx: &mut ViewContext<Workspace>,
4343 default_content: impl 'static + Send + FnOnce() -> Rope,
4344) -> Task<Result<Box<dyn ItemHandle>>> {
4345 cx.spawn(|workspace, mut cx| async move {
4346 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4347 if !fs.is_file(path).await {
4348 fs.create_file(path, Default::default()).await?;
4349 fs.save(path, &default_content(), Default::default())
4350 .await?;
4351 }
4352
4353 let mut items = workspace
4354 .update(&mut cx, |workspace, cx| {
4355 workspace.with_local_workspace(cx, |workspace, cx| {
4356 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4357 })
4358 })?
4359 .await?
4360 .await;
4361
4362 let item = items.pop().flatten();
4363 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4364 })
4365}
4366
4367// pub fn join_remote_project(
4368// project_id: u64,
4369// follow_user_id: u64,
4370// app_state: Arc<AppState>,
4371// cx: &mut AppContext,
4372// ) -> Task<Result<()>> {
4373// cx.spawn(|mut cx| async move {
4374// let windows = cx.windows();
4375// let existing_workspace = windows.into_iter().find_map(|window| {
4376// window.downcast::<Workspace>().and_then(|window| {
4377// window
4378// .read_root_with(&cx, |workspace, cx| {
4379// if workspace.project().read(cx).remote_id() == Some(project_id) {
4380// Some(cx.handle().downgrade())
4381// } else {
4382// None
4383// }
4384// })
4385// .unwrap_or(None)
4386// })
4387// });
4388
4389// let workspace = if let Some(existing_workspace) = existing_workspace {
4390// existing_workspace
4391// } else {
4392// let active_call = cx.read(ActiveCall::global);
4393// let room = active_call
4394// .read_with(&cx, |call, _| call.room().cloned())
4395// .ok_or_else(|| anyhow!("not in a call"))?;
4396// let project = room
4397// .update(&mut cx, |room, cx| {
4398// room.join_project(
4399// project_id,
4400// app_state.languages.clone(),
4401// app_state.fs.clone(),
4402// cx,
4403// )
4404// })
4405// .await?;
4406
4407// let window_bounds_override = window_bounds_env_override(&cx);
4408// let window = cx.add_window(
4409// (app_state.build_window_options)(
4410// window_bounds_override,
4411// None,
4412// cx.platform().as_ref(),
4413// ),
4414// |cx| Workspace::new(0, project, app_state.clone(), cx),
4415// );
4416// let workspace = window.root(&cx).unwrap();
4417// (app_state.initialize_workspace)(
4418// workspace.downgrade(),
4419// false,
4420// app_state.clone(),
4421// cx.clone(),
4422// )
4423// .await
4424// .log_err();
4425
4426// workspace.downgrade()
4427// };
4428
4429// workspace.window().activate(&mut cx);
4430// cx.platform().activate(true);
4431
4432// workspace.update(&mut cx, |workspace, cx| {
4433// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4434// let follow_peer_id = room
4435// .read(cx)
4436// .remote_participants()
4437// .iter()
4438// .find(|(_, participant)| participant.user.id == follow_user_id)
4439// .map(|(_, p)| p.peer_id)
4440// .or_else(|| {
4441// // If we couldn't follow the given user, follow the host instead.
4442// let collaborator = workspace
4443// .project()
4444// .read(cx)
4445// .collaborators()
4446// .values()
4447// .find(|collaborator| collaborator.replica_id == 0)?;
4448// Some(collaborator.peer_id)
4449// });
4450
4451// if let Some(follow_peer_id) = follow_peer_id {
4452// workspace
4453// .follow(follow_peer_id, cx)
4454// .map(|follow| follow.detach_and_log_err(cx));
4455// }
4456// }
4457// })?;
4458
4459// anyhow::Ok(())
4460// })
4461// }
4462
4463// pub fn restart(_: &Restart, cx: &mut AppContext) {
4464// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4465// cx.spawn(|mut cx| async move {
4466// let mut workspace_windows = cx
4467// .windows()
4468// .into_iter()
4469// .filter_map(|window| window.downcast::<Workspace>())
4470// .collect::<Vec<_>>();
4471
4472// // If multiple windows have unsaved changes, and need a save prompt,
4473// // prompt in the active window before switching to a different window.
4474// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4475
4476// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4477// let answer = window.prompt(
4478// PromptLevel::Info,
4479// "Are you sure you want to restart?",
4480// &["Restart", "Cancel"],
4481// &mut cx,
4482// );
4483
4484// if let Some(mut answer) = answer {
4485// let answer = answer.next().await;
4486// if answer != Some(0) {
4487// return Ok(());
4488// }
4489// }
4490// }
4491
4492// // If the user cancels any save prompt, then keep the app open.
4493// for window in workspace_windows {
4494// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4495// workspace.prepare_to_close(true, cx)
4496// }) {
4497// if !should_close.await? {
4498// return Ok(());
4499// }
4500// }
4501// }
4502// cx.platform().restart();
4503// anyhow::Ok(())
4504// })
4505// .detach_and_log_err(cx);
4506// }
4507
4508fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4509 let mut parts = value.split(',');
4510 let x: usize = parts.next()?.parse().ok()?;
4511 let y: usize = parts.next()?.parse().ok()?;
4512 Some(point((x as f64).into(), (y as f64).into()))
4513}
4514
4515fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4516 let mut parts = value.split(',');
4517 let width: usize = parts.next()?.parse().ok()?;
4518 let height: usize = parts.next()?.parse().ok()?;
4519 Some(size((width as f64).into(), (height as f64).into()))
4520}
4521
4522// #[cfg(test)]
4523// mod tests {
4524// use super::*;
4525// use crate::{
4526// dock::test::{TestPanel, TestPanelEvent},
4527// item::test::{TestItem, TestItemEvent, TestProjectItem},
4528// };
4529// use fs::FakeFs;
4530// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4531// use project::{Project, ProjectEntryId};
4532// use serde_json::json;
4533// use settings::SettingsStore;
4534// use std::{cell::RefCell, rc::Rc};
4535
4536// #[gpui::test]
4537// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4538// init_test(cx);
4539
4540// let fs = FakeFs::new(cx.background());
4541// let project = Project::test(fs, [], cx).await;
4542// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4543// let workspace = window.root(cx);
4544
4545// // Adding an item with no ambiguity renders the tab without detail.
4546// let item1 = window.build_view(cx, |_| {
4547// let mut item = TestItem::new();
4548// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4549// item
4550// });
4551// workspace.update(cx, |workspace, cx| {
4552// workspace.add_item(Box::new(item1.clone()), cx);
4553// });
4554// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4555
4556// // Adding an item that creates ambiguity increases the level of detail on
4557// // both tabs.
4558// let item2 = window.build_view(cx, |_| {
4559// let mut item = TestItem::new();
4560// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4561// item
4562// });
4563// workspace.update(cx, |workspace, cx| {
4564// workspace.add_item(Box::new(item2.clone()), cx);
4565// });
4566// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4567// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4568
4569// // Adding an item that creates ambiguity increases the level of detail only
4570// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4571// // we stop at the highest detail available.
4572// let item3 = window.build_view(cx, |_| {
4573// let mut item = TestItem::new();
4574// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4575// item
4576// });
4577// workspace.update(cx, |workspace, cx| {
4578// workspace.add_item(Box::new(item3.clone()), cx);
4579// });
4580// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4581// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4582// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4583// }
4584
4585// #[gpui::test]
4586// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4587// init_test(cx);
4588
4589// let fs = FakeFs::new(cx.background());
4590// fs.insert_tree(
4591// "/root1",
4592// json!({
4593// "one.txt": "",
4594// "two.txt": "",
4595// }),
4596// )
4597// .await;
4598// fs.insert_tree(
4599// "/root2",
4600// json!({
4601// "three.txt": "",
4602// }),
4603// )
4604// .await;
4605
4606// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4607// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4608// let workspace = window.root(cx);
4609// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4610// let worktree_id = project.read_with(cx, |project, cx| {
4611// project.worktrees(cx).next().unwrap().read(cx).id()
4612// });
4613
4614// let item1 = window.build_view(cx, |cx| {
4615// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4616// });
4617// let item2 = window.build_view(cx, |cx| {
4618// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4619// });
4620
4621// // Add an item to an empty pane
4622// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4623// project.read_with(cx, |project, cx| {
4624// assert_eq!(
4625// project.active_entry(),
4626// project
4627// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4628// .map(|e| e.id)
4629// );
4630// });
4631// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4632
4633// // Add a second item to a non-empty pane
4634// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4635// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4636// project.read_with(cx, |project, cx| {
4637// assert_eq!(
4638// project.active_entry(),
4639// project
4640// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4641// .map(|e| e.id)
4642// );
4643// });
4644
4645// // Close the active item
4646// pane.update(cx, |pane, cx| {
4647// pane.close_active_item(&Default::default(), cx).unwrap()
4648// })
4649// .await
4650// .unwrap();
4651// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4652// project.read_with(cx, |project, cx| {
4653// assert_eq!(
4654// project.active_entry(),
4655// project
4656// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4657// .map(|e| e.id)
4658// );
4659// });
4660
4661// // Add a project folder
4662// project
4663// .update(cx, |project, cx| {
4664// project.find_or_create_local_worktree("/root2", true, cx)
4665// })
4666// .await
4667// .unwrap();
4668// assert_eq!(
4669// window.current_title(cx).as_deref(),
4670// Some("one.txt β root1, root2")
4671// );
4672
4673// // Remove a project folder
4674// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4675// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4676// }
4677
4678// #[gpui::test]
4679// async fn test_close_window(cx: &mut TestAppContext) {
4680// init_test(cx);
4681
4682// let fs = FakeFs::new(cx.background());
4683// fs.insert_tree("/root", json!({ "one": "" })).await;
4684
4685// let project = Project::test(fs, ["root".as_ref()], cx).await;
4686// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4687// let workspace = window.root(cx);
4688
4689// // When there are no dirty items, there's nothing to do.
4690// let item1 = window.build_view(cx, |_| TestItem::new());
4691// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4692// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4693// assert!(task.await.unwrap());
4694
4695// // When there are dirty untitled items, prompt to save each one. If the user
4696// // cancels any prompt, then abort.
4697// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4698// let item3 = window.build_view(cx, |cx| {
4699// TestItem::new()
4700// .with_dirty(true)
4701// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4702// });
4703// workspace.update(cx, |w, cx| {
4704// w.add_item(Box::new(item2.clone()), cx);
4705// w.add_item(Box::new(item3.clone()), cx);
4706// });
4707// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4708// cx.foreground().run_until_parked();
4709// window.simulate_prompt_answer(2, cx); // cancel save all
4710// cx.foreground().run_until_parked();
4711// window.simulate_prompt_answer(2, cx); // cancel save all
4712// cx.foreground().run_until_parked();
4713// assert!(!window.has_pending_prompt(cx));
4714// assert!(!task.await.unwrap());
4715// }
4716
4717// #[gpui::test]
4718// async fn test_close_pane_items(cx: &mut TestAppContext) {
4719// init_test(cx);
4720
4721// let fs = FakeFs::new(cx.background());
4722
4723// let project = Project::test(fs, None, cx).await;
4724// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4725// let workspace = window.root(cx);
4726
4727// let item1 = window.build_view(cx, |cx| {
4728// TestItem::new()
4729// .with_dirty(true)
4730// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4731// });
4732// let item2 = window.build_view(cx, |cx| {
4733// TestItem::new()
4734// .with_dirty(true)
4735// .with_conflict(true)
4736// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4737// });
4738// let item3 = window.build_view(cx, |cx| {
4739// TestItem::new()
4740// .with_dirty(true)
4741// .with_conflict(true)
4742// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4743// });
4744// let item4 = window.build_view(cx, |cx| {
4745// TestItem::new()
4746// .with_dirty(true)
4747// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4748// });
4749// let pane = workspace.update(cx, |workspace, cx| {
4750// workspace.add_item(Box::new(item1.clone()), cx);
4751// workspace.add_item(Box::new(item2.clone()), cx);
4752// workspace.add_item(Box::new(item3.clone()), cx);
4753// workspace.add_item(Box::new(item4.clone()), cx);
4754// workspace.active_pane().clone()
4755// });
4756
4757// let close_items = pane.update(cx, |pane, cx| {
4758// pane.activate_item(1, true, true, cx);
4759// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4760// let item1_id = item1.id();
4761// let item3_id = item3.id();
4762// let item4_id = item4.id();
4763// pane.close_items(cx, SaveIntent::Close, move |id| {
4764// [item1_id, item3_id, item4_id].contains(&id)
4765// })
4766// });
4767// cx.foreground().run_until_parked();
4768
4769// assert!(window.has_pending_prompt(cx));
4770// // Ignore "Save all" prompt
4771// window.simulate_prompt_answer(2, cx);
4772// cx.foreground().run_until_parked();
4773// // There's a prompt to save item 1.
4774// pane.read_with(cx, |pane, _| {
4775// assert_eq!(pane.items_len(), 4);
4776// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4777// });
4778// // Confirm saving item 1.
4779// window.simulate_prompt_answer(0, cx);
4780// cx.foreground().run_until_parked();
4781
4782// // Item 1 is saved. There's a prompt to save item 3.
4783// pane.read_with(cx, |pane, cx| {
4784// assert_eq!(item1.read(cx).save_count, 1);
4785// assert_eq!(item1.read(cx).save_as_count, 0);
4786// assert_eq!(item1.read(cx).reload_count, 0);
4787// assert_eq!(pane.items_len(), 3);
4788// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4789// });
4790// assert!(window.has_pending_prompt(cx));
4791
4792// // Cancel saving item 3.
4793// window.simulate_prompt_answer(1, cx);
4794// cx.foreground().run_until_parked();
4795
4796// // Item 3 is reloaded. There's a prompt to save item 4.
4797// pane.read_with(cx, |pane, cx| {
4798// assert_eq!(item3.read(cx).save_count, 0);
4799// assert_eq!(item3.read(cx).save_as_count, 0);
4800// assert_eq!(item3.read(cx).reload_count, 1);
4801// assert_eq!(pane.items_len(), 2);
4802// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4803// });
4804// assert!(window.has_pending_prompt(cx));
4805
4806// // Confirm saving item 4.
4807// window.simulate_prompt_answer(0, cx);
4808// cx.foreground().run_until_parked();
4809
4810// // There's a prompt for a path for item 4.
4811// cx.simulate_new_path_selection(|_| Some(Default::default()));
4812// close_items.await.unwrap();
4813
4814// // The requested items are closed.
4815// pane.read_with(cx, |pane, cx| {
4816// assert_eq!(item4.read(cx).save_count, 0);
4817// assert_eq!(item4.read(cx).save_as_count, 1);
4818// assert_eq!(item4.read(cx).reload_count, 0);
4819// assert_eq!(pane.items_len(), 1);
4820// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4821// });
4822// }
4823
4824// #[gpui::test]
4825// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4826// init_test(cx);
4827
4828// let fs = FakeFs::new(cx.background());
4829
4830// let project = Project::test(fs, [], cx).await;
4831// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4832// let workspace = window.root(cx);
4833
4834// // Create several workspace items with single project entries, and two
4835// // workspace items with multiple project entries.
4836// let single_entry_items = (0..=4)
4837// .map(|project_entry_id| {
4838// window.build_view(cx, |cx| {
4839// TestItem::new()
4840// .with_dirty(true)
4841// .with_project_items(&[TestProjectItem::new(
4842// project_entry_id,
4843// &format!("{project_entry_id}.txt"),
4844// cx,
4845// )])
4846// })
4847// })
4848// .collect::<Vec<_>>();
4849// let item_2_3 = window.build_view(cx, |cx| {
4850// TestItem::new()
4851// .with_dirty(true)
4852// .with_singleton(false)
4853// .with_project_items(&[
4854// single_entry_items[2].read(cx).project_items[0].clone(),
4855// single_entry_items[3].read(cx).project_items[0].clone(),
4856// ])
4857// });
4858// let item_3_4 = window.build_view(cx, |cx| {
4859// TestItem::new()
4860// .with_dirty(true)
4861// .with_singleton(false)
4862// .with_project_items(&[
4863// single_entry_items[3].read(cx).project_items[0].clone(),
4864// single_entry_items[4].read(cx).project_items[0].clone(),
4865// ])
4866// });
4867
4868// // Create two panes that contain the following project entries:
4869// // left pane:
4870// // multi-entry items: (2, 3)
4871// // single-entry items: 0, 1, 2, 3, 4
4872// // right pane:
4873// // single-entry items: 1
4874// // multi-entry items: (3, 4)
4875// let left_pane = workspace.update(cx, |workspace, cx| {
4876// let left_pane = workspace.active_pane().clone();
4877// workspace.add_item(Box::new(item_2_3.clone()), cx);
4878// for item in single_entry_items {
4879// workspace.add_item(Box::new(item), cx);
4880// }
4881// left_pane.update(cx, |pane, cx| {
4882// pane.activate_item(2, true, true, cx);
4883// });
4884
4885// workspace
4886// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4887// .unwrap();
4888
4889// left_pane
4890// });
4891
4892// //Need to cause an effect flush in order to respect new focus
4893// workspace.update(cx, |workspace, cx| {
4894// workspace.add_item(Box::new(item_3_4.clone()), cx);
4895// cx.focus(&left_pane);
4896// });
4897
4898// // When closing all of the items in the left pane, we should be prompted twice:
4899// // once for project entry 0, and once for project entry 2. After those two
4900// // prompts, the task should complete.
4901
4902// let close = left_pane.update(cx, |pane, cx| {
4903// pane.close_items(cx, SaveIntent::Close, move |_| true)
4904// });
4905// cx.foreground().run_until_parked();
4906// // Discard "Save all" prompt
4907// window.simulate_prompt_answer(2, cx);
4908
4909// cx.foreground().run_until_parked();
4910// left_pane.read_with(cx, |pane, cx| {
4911// assert_eq!(
4912// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4913// &[ProjectEntryId::from_proto(0)]
4914// );
4915// });
4916// window.simulate_prompt_answer(0, cx);
4917
4918// cx.foreground().run_until_parked();
4919// left_pane.read_with(cx, |pane, cx| {
4920// assert_eq!(
4921// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4922// &[ProjectEntryId::from_proto(2)]
4923// );
4924// });
4925// window.simulate_prompt_answer(0, cx);
4926
4927// cx.foreground().run_until_parked();
4928// close.await.unwrap();
4929// left_pane.read_with(cx, |pane, _| {
4930// assert_eq!(pane.items_len(), 0);
4931// });
4932// }
4933
4934// #[gpui::test]
4935// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4936// init_test(cx);
4937
4938// let fs = FakeFs::new(cx.background());
4939
4940// let project = Project::test(fs, [], cx).await;
4941// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4942// let workspace = window.root(cx);
4943// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4944
4945// let item = window.build_view(cx, |cx| {
4946// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4947// });
4948// let item_id = item.id();
4949// workspace.update(cx, |workspace, cx| {
4950// workspace.add_item(Box::new(item.clone()), cx);
4951// });
4952
4953// // Autosave on window change.
4954// item.update(cx, |item, cx| {
4955// cx.update_global(|settings: &mut SettingsStore, cx| {
4956// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4957// settings.autosave = Some(AutosaveSetting::OnWindowChange);
4958// })
4959// });
4960// item.is_dirty = true;
4961// });
4962
4963// // Deactivating the window saves the file.
4964// window.simulate_deactivation(cx);
4965// deterministic.run_until_parked();
4966// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4967
4968// // Autosave on focus change.
4969// item.update(cx, |item, cx| {
4970// cx.focus_self();
4971// cx.update_global(|settings: &mut SettingsStore, cx| {
4972// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4973// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4974// })
4975// });
4976// item.is_dirty = true;
4977// });
4978
4979// // Blurring the item saves the file.
4980// item.update(cx, |_, cx| cx.blur());
4981// deterministic.run_until_parked();
4982// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4983
4984// // Deactivating the window still saves the file.
4985// window.simulate_activation(cx);
4986// item.update(cx, |item, cx| {
4987// cx.focus_self();
4988// item.is_dirty = true;
4989// });
4990// window.simulate_deactivation(cx);
4991
4992// deterministic.run_until_parked();
4993// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4994
4995// // Autosave after delay.
4996// item.update(cx, |item, cx| {
4997// cx.update_global(|settings: &mut SettingsStore, cx| {
4998// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4999// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
5000// })
5001// });
5002// item.is_dirty = true;
5003// cx.emit(TestItemEvent::Edit);
5004// });
5005
5006// // Delay hasn't fully expired, so the file is still dirty and unsaved.
5007// deterministic.advance_clock(Duration::from_millis(250));
5008// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
5009
5010// // After delay expires, the file is saved.
5011// deterministic.advance_clock(Duration::from_millis(250));
5012// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
5013
5014// // Autosave on focus change, ensuring closing the tab counts as such.
5015// item.update(cx, |item, cx| {
5016// cx.update_global(|settings: &mut SettingsStore, cx| {
5017// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
5018// settings.autosave = Some(AutosaveSetting::OnFocusChange);
5019// })
5020// });
5021// item.is_dirty = true;
5022// });
5023
5024// pane.update(cx, |pane, cx| {
5025// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5026// })
5027// .await
5028// .unwrap();
5029// assert!(!window.has_pending_prompt(cx));
5030// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5031
5032// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
5033// workspace.update(cx, |workspace, cx| {
5034// workspace.add_item(Box::new(item.clone()), cx);
5035// });
5036// item.update(cx, |item, cx| {
5037// item.project_items[0].update(cx, |item, _| {
5038// item.entry_id = None;
5039// });
5040// item.is_dirty = true;
5041// cx.blur();
5042// });
5043// deterministic.run_until_parked();
5044// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5045
5046// // Ensure autosave is prevented for deleted files also when closing the buffer.
5047// let _close_items = pane.update(cx, |pane, cx| {
5048// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
5049// });
5050// deterministic.run_until_parked();
5051// assert!(window.has_pending_prompt(cx));
5052// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
5053// }
5054
5055// #[gpui::test]
5056// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
5057// init_test(cx);
5058
5059// let fs = FakeFs::new(cx.background());
5060
5061// let project = Project::test(fs, [], cx).await;
5062// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5063// let workspace = window.root(cx);
5064
5065// let item = window.build_view(cx, |cx| {
5066// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5067// });
5068// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5069// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5070// let toolbar_notify_count = Rc::new(RefCell::new(0));
5071
5072// workspace.update(cx, |workspace, cx| {
5073// workspace.add_item(Box::new(item.clone()), cx);
5074// let toolbar_notification_count = toolbar_notify_count.clone();
5075// cx.observe(&toolbar, move |_, _, _| {
5076// *toolbar_notification_count.borrow_mut() += 1
5077// })
5078// .detach();
5079// });
5080
5081// pane.read_with(cx, |pane, _| {
5082// assert!(!pane.can_navigate_backward());
5083// assert!(!pane.can_navigate_forward());
5084// });
5085
5086// item.update(cx, |item, cx| {
5087// item.set_state("one".to_string(), cx);
5088// });
5089
5090// // Toolbar must be notified to re-render the navigation buttons
5091// assert_eq!(*toolbar_notify_count.borrow(), 1);
5092
5093// pane.read_with(cx, |pane, _| {
5094// assert!(pane.can_navigate_backward());
5095// assert!(!pane.can_navigate_forward());
5096// });
5097
5098// workspace
5099// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5100// .await
5101// .unwrap();
5102
5103// assert_eq!(*toolbar_notify_count.borrow(), 3);
5104// pane.read_with(cx, |pane, _| {
5105// assert!(!pane.can_navigate_backward());
5106// assert!(pane.can_navigate_forward());
5107// });
5108// }
5109
5110// #[gpui::test]
5111// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5112// init_test(cx);
5113// let fs = FakeFs::new(cx.background());
5114
5115// let project = Project::test(fs, [], cx).await;
5116// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5117// let workspace = window.root(cx);
5118
5119// let panel = workspace.update(cx, |workspace, cx| {
5120// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5121// workspace.add_panel(panel.clone(), cx);
5122
5123// workspace
5124// .right_dock()
5125// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5126
5127// panel
5128// });
5129
5130// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5131// pane.update(cx, |pane, cx| {
5132// let item = cx.build_view(|_| TestItem::new());
5133// pane.add_item(Box::new(item), true, true, None, cx);
5134// });
5135
5136// // Transfer focus from center to panel
5137// workspace.update(cx, |workspace, cx| {
5138// workspace.toggle_panel_focus::<TestPanel>(cx);
5139// });
5140
5141// workspace.read_with(cx, |workspace, cx| {
5142// assert!(workspace.right_dock().read(cx).is_open());
5143// assert!(!panel.is_zoomed(cx));
5144// assert!(panel.has_focus(cx));
5145// });
5146
5147// // Transfer focus from panel to center
5148// workspace.update(cx, |workspace, cx| {
5149// workspace.toggle_panel_focus::<TestPanel>(cx);
5150// });
5151
5152// workspace.read_with(cx, |workspace, cx| {
5153// assert!(workspace.right_dock().read(cx).is_open());
5154// assert!(!panel.is_zoomed(cx));
5155// assert!(!panel.has_focus(cx));
5156// });
5157
5158// // Close the dock
5159// workspace.update(cx, |workspace, cx| {
5160// workspace.toggle_dock(DockPosition::Right, cx);
5161// });
5162
5163// workspace.read_with(cx, |workspace, cx| {
5164// assert!(!workspace.right_dock().read(cx).is_open());
5165// assert!(!panel.is_zoomed(cx));
5166// assert!(!panel.has_focus(cx));
5167// });
5168
5169// // Open the dock
5170// workspace.update(cx, |workspace, cx| {
5171// workspace.toggle_dock(DockPosition::Right, cx);
5172// });
5173
5174// workspace.read_with(cx, |workspace, cx| {
5175// assert!(workspace.right_dock().read(cx).is_open());
5176// assert!(!panel.is_zoomed(cx));
5177// assert!(panel.has_focus(cx));
5178// });
5179
5180// // Focus and zoom panel
5181// panel.update(cx, |panel, cx| {
5182// cx.focus_self();
5183// panel.set_zoomed(true, 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// // Transfer focus to the center closes the dock
5193// workspace.update(cx, |workspace, cx| {
5194// workspace.toggle_panel_focus::<TestPanel>(cx);
5195// });
5196
5197// workspace.read_with(cx, |workspace, cx| {
5198// assert!(!workspace.right_dock().read(cx).is_open());
5199// assert!(panel.is_zoomed(cx));
5200// assert!(!panel.has_focus(cx));
5201// });
5202
5203// // Transferring focus back to the panel keeps it zoomed
5204// workspace.update(cx, |workspace, cx| {
5205// workspace.toggle_panel_focus::<TestPanel>(cx);
5206// });
5207
5208// workspace.read_with(cx, |workspace, cx| {
5209// assert!(workspace.right_dock().read(cx).is_open());
5210// assert!(panel.is_zoomed(cx));
5211// assert!(panel.has_focus(cx));
5212// });
5213
5214// // Close the dock while it is zoomed
5215// workspace.update(cx, |workspace, cx| {
5216// workspace.toggle_dock(DockPosition::Right, cx)
5217// });
5218
5219// workspace.read_with(cx, |workspace, cx| {
5220// assert!(!workspace.right_dock().read(cx).is_open());
5221// assert!(panel.is_zoomed(cx));
5222// assert!(workspace.zoomed.is_none());
5223// assert!(!panel.has_focus(cx));
5224// });
5225
5226// // Opening the dock, when it's zoomed, retains focus
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_some());
5235// assert!(panel.has_focus(cx));
5236// });
5237
5238// // Unzoom and close the panel, zoom the active pane.
5239// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5240// workspace.update(cx, |workspace, cx| {
5241// workspace.toggle_dock(DockPosition::Right, cx)
5242// });
5243// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5244
5245// // Opening a dock unzooms the pane.
5246// workspace.update(cx, |workspace, cx| {
5247// workspace.toggle_dock(DockPosition::Right, cx)
5248// });
5249// workspace.read_with(cx, |workspace, cx| {
5250// let pane = pane.read(cx);
5251// assert!(!pane.is_zoomed());
5252// assert!(!pane.has_focus());
5253// assert!(workspace.right_dock().read(cx).is_open());
5254// assert!(workspace.zoomed.is_none());
5255// });
5256// }
5257
5258// #[gpui::test]
5259// async fn test_panels(cx: &mut gpui::TestAppContext) {
5260// init_test(cx);
5261// let fs = FakeFs::new(cx.background());
5262
5263// let project = Project::test(fs, [], cx).await;
5264// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5265// let workspace = window.root(cx);
5266
5267// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5268// // Add panel_1 on the left, panel_2 on the right.
5269// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5270// workspace.add_panel(panel_1.clone(), cx);
5271// workspace
5272// .left_dock()
5273// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5274// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5275// workspace.add_panel(panel_2.clone(), cx);
5276// workspace
5277// .right_dock()
5278// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5279
5280// let left_dock = workspace.left_dock();
5281// assert_eq!(
5282// left_dock.read(cx).visible_panel().unwrap().id(),
5283// panel_1.id()
5284// );
5285// assert_eq!(
5286// left_dock.read(cx).active_panel_size(cx).unwrap(),
5287// panel_1.size(cx)
5288// );
5289
5290// left_dock.update(cx, |left_dock, cx| {
5291// left_dock.resize_active_panel(Some(1337.), cx)
5292// });
5293// assert_eq!(
5294// workspace
5295// .right_dock()
5296// .read(cx)
5297// .visible_panel()
5298// .unwrap()
5299// .id(),
5300// panel_2.id()
5301// );
5302
5303// (panel_1, panel_2)
5304// });
5305
5306// // Move panel_1 to the right
5307// panel_1.update(cx, |panel_1, cx| {
5308// panel_1.set_position(DockPosition::Right, cx)
5309// });
5310
5311// workspace.update(cx, |workspace, cx| {
5312// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5313// // Since it was the only panel on the left, the left dock should now be closed.
5314// assert!(!workspace.left_dock().read(cx).is_open());
5315// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5316// let right_dock = workspace.right_dock();
5317// assert_eq!(
5318// right_dock.read(cx).visible_panel().unwrap().id(),
5319// panel_1.id()
5320// );
5321// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5322
5323// // Now we move panel_2Β to the left
5324// panel_2.set_position(DockPosition::Left, cx);
5325// });
5326
5327// workspace.update(cx, |workspace, cx| {
5328// // Since panel_2 was not visible on the right, we don't open the left dock.
5329// assert!(!workspace.left_dock().read(cx).is_open());
5330// // And the right dock is unaffected in it's displaying of panel_1
5331// assert!(workspace.right_dock().read(cx).is_open());
5332// assert_eq!(
5333// workspace
5334// .right_dock()
5335// .read(cx)
5336// .visible_panel()
5337// .unwrap()
5338// .id(),
5339// panel_1.id()
5340// );
5341// });
5342
5343// // Move panel_1 back to the left
5344// panel_1.update(cx, |panel_1, cx| {
5345// panel_1.set_position(DockPosition::Left, cx)
5346// });
5347
5348// workspace.update(cx, |workspace, cx| {
5349// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5350// let left_dock = workspace.left_dock();
5351// assert!(left_dock.read(cx).is_open());
5352// assert_eq!(
5353// left_dock.read(cx).visible_panel().unwrap().id(),
5354// panel_1.id()
5355// );
5356// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5357// // And right the dock should be closed as it no longer has any panels.
5358// assert!(!workspace.right_dock().read(cx).is_open());
5359
5360// // Now we move panel_1 to the bottom
5361// panel_1.set_position(DockPosition::Bottom, cx);
5362// });
5363
5364// workspace.update(cx, |workspace, cx| {
5365// // Since panel_1 was visible on the left, we close the left dock.
5366// assert!(!workspace.left_dock().read(cx).is_open());
5367// // The bottom dock is sized based on the panel's default size,
5368// // since the panel orientation changed from vertical to horizontal.
5369// let bottom_dock = workspace.bottom_dock();
5370// assert_eq!(
5371// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5372// panel_1.size(cx),
5373// );
5374// // Close bottom dock and move panel_1 back to the left.
5375// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5376// panel_1.set_position(DockPosition::Left, cx);
5377// });
5378
5379// // Emit activated event on panel 1
5380// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5381
5382// // Now the left dock is open and panel_1 is active and focused.
5383// workspace.read_with(cx, |workspace, cx| {
5384// let left_dock = workspace.left_dock();
5385// assert!(left_dock.read(cx).is_open());
5386// assert_eq!(
5387// left_dock.read(cx).visible_panel().unwrap().id(),
5388// panel_1.id()
5389// );
5390// assert!(panel_1.is_focused(cx));
5391// });
5392
5393// // Emit closed event on panel 2, which is not active
5394// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5395
5396// // Wo don't close the left dock, because panel_2 wasn't the active panel
5397// workspace.read_with(cx, |workspace, cx| {
5398// let left_dock = workspace.left_dock();
5399// assert!(left_dock.read(cx).is_open());
5400// assert_eq!(
5401// left_dock.read(cx).visible_panel().unwrap().id(),
5402// panel_1.id()
5403// );
5404// });
5405
5406// // Emitting a ZoomIn event shows the panel as zoomed.
5407// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5408// workspace.read_with(cx, |workspace, _| {
5409// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5410// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5411// });
5412
5413// // Move panel to another dock while it is zoomed
5414// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5415// workspace.read_with(cx, |workspace, _| {
5416// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5417// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5418// });
5419
5420// // If focus is transferred to another view that's not a panel or another pane, we still show
5421// // the panel as zoomed.
5422// let focus_receiver = window.build_view(cx, |_| EmptyView);
5423// focus_receiver.update(cx, |_, cx| cx.focus_self());
5424// workspace.read_with(cx, |workspace, _| {
5425// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5426// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5427// });
5428
5429// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5430// workspace.update(cx, |_, cx| cx.focus_self());
5431// workspace.read_with(cx, |workspace, _| {
5432// assert_eq!(workspace.zoomed, None);
5433// assert_eq!(workspace.zoomed_position, None);
5434// });
5435
5436// // If focus is transferred again to another view that's not a panel or a pane, we won't
5437// // show the panel as zoomed because it wasn't zoomed before.
5438// focus_receiver.update(cx, |_, cx| cx.focus_self());
5439// workspace.read_with(cx, |workspace, _| {
5440// assert_eq!(workspace.zoomed, None);
5441// assert_eq!(workspace.zoomed_position, None);
5442// });
5443
5444// // When focus is transferred back to the panel, it is zoomed again.
5445// panel_1.update(cx, |_, cx| cx.focus_self());
5446// workspace.read_with(cx, |workspace, _| {
5447// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5448// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5449// });
5450
5451// // Emitting a ZoomOut event unzooms the panel.
5452// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5453// workspace.read_with(cx, |workspace, _| {
5454// assert_eq!(workspace.zoomed, None);
5455// assert_eq!(workspace.zoomed_position, None);
5456// });
5457
5458// // Emit closed event on panel 1, which is active
5459// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5460
5461// // Now the left dock is closed, because panel_1 was the active panel
5462// workspace.read_with(cx, |workspace, cx| {
5463// let right_dock = workspace.right_dock();
5464// assert!(!right_dock.read(cx).is_open());
5465// });
5466// }
5467
5468// pub fn init_test(cx: &mut TestAppContext) {
5469// cx.foreground().forbid_parking();
5470// cx.update(|cx| {
5471// cx.set_global(SettingsStore::test(cx));
5472// theme::init((), cx);
5473// language::init(cx);
5474// crate::init_settings(cx);
5475// Project::init_settings(cx);
5476// });
5477// }
5478// }