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