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