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