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