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