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