1#![allow(unused_variables, dead_code, unused_mut)]
2// todo!() this is to make transition easier.
3
4pub mod dock;
5pub mod item;
6pub mod notifications;
7pub mod pane;
8pub mod pane_group;
9mod persistence;
10pub mod searchable;
11// todo!()
12mod modal_layer;
13pub mod shared_screen;
14mod status_bar;
15mod toolbar;
16mod workspace_settings;
17
18use anyhow::{anyhow, Context as _, Result};
19use call::ActiveCall;
20use client::{
21 proto::{self, PeerId},
22 Client, Status, TypedEnvelope, UserStore,
23};
24use collections::{hash_map, HashMap, HashSet};
25use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
26use futures::{
27 channel::{mpsc, oneshot},
28 future::try_join_all,
29 Future, FutureExt, StreamExt,
30};
31use gpui::{
32 actions, 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 fn load_path(
1857 &mut self,
1858 path: ProjectPath,
1859 cx: &mut ViewContext<Self>,
1860 ) -> Task<
1861 Result<(
1862 Option<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 fn activate_workspace_for_project(
3946 cx: &mut AppContext,
3947 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3948) -> Option<WindowHandle<Workspace>> {
3949 for window in cx.windows() {
3950 let Some(workspace) = window.downcast::<Workspace>() else {
3951 continue;
3952 };
3953
3954 let predicate = workspace
3955 .update(cx, |workspace, cx| {
3956 let project = workspace.project.read(cx);
3957 if predicate(project, cx) {
3958 cx.activate_window();
3959 true
3960 } else {
3961 false
3962 }
3963 })
3964 .log_err()
3965 .unwrap_or(false);
3966
3967 if predicate {
3968 return Some(workspace);
3969 }
3970 }
3971
3972 None
3973}
3974
3975pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3976 DB.last_workspace().await.log_err().flatten()
3977}
3978
3979async fn join_channel_internal(
3980 channel_id: u64,
3981 app_state: &Arc<AppState>,
3982 requesting_window: Option<WindowHandle<Workspace>>,
3983 active_call: &Model<ActiveCall>,
3984 cx: &mut AsyncAppContext,
3985) -> Result<bool> {
3986 let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
3987 let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3988 return (false, None);
3989 };
3990
3991 let already_in_channel = room.channel_id() == Some(channel_id);
3992 let should_prompt = room.is_sharing_project()
3993 && room.remote_participants().len() > 0
3994 && !already_in_channel;
3995 let open_room = if already_in_channel {
3996 active_call.room().cloned()
3997 } else {
3998 None
3999 };
4000 (should_prompt, open_room)
4001 })?;
4002
4003 if let Some(room) = open_room {
4004 let task = room.update(cx, |room, cx| {
4005 if let Some((project, host)) = room.most_active_project(cx) {
4006 return Some(join_remote_project(project, host, app_state.clone(), cx));
4007 }
4008
4009 None
4010 })?;
4011 if let Some(task) = task {
4012 task.await?;
4013 }
4014 return anyhow::Ok(true);
4015 }
4016
4017 if should_prompt {
4018 if let Some(workspace) = requesting_window {
4019 let answer = workspace.update(cx, |_, cx| {
4020 cx.prompt(
4021 PromptLevel::Warning,
4022 "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4023 &["Yes, Join Channel", "Cancel"],
4024 )
4025 })?.await;
4026
4027 if answer == Ok(1) {
4028 return Ok(false);
4029 }
4030 } else {
4031 return Ok(false); // unreachable!() hopefully
4032 }
4033 }
4034
4035 let client = cx.update(|cx| active_call.read(cx).client())?;
4036
4037 let mut client_status = client.status();
4038
4039 // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4040 'outer: loop {
4041 let Some(status) = client_status.recv().await else {
4042 return Err(anyhow!("error connecting"));
4043 };
4044
4045 match status {
4046 Status::Connecting
4047 | Status::Authenticating
4048 | Status::Reconnecting
4049 | Status::Reauthenticating => continue,
4050 Status::Connected { .. } => break 'outer,
4051 Status::SignedOut => return Err(anyhow!("not signed in")),
4052 Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4053 Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4054 return Err(anyhow!("zed is offline"))
4055 }
4056 }
4057 }
4058
4059 let room = active_call
4060 .update(cx, |active_call, cx| {
4061 active_call.join_channel(channel_id, cx)
4062 })?
4063 .await?;
4064
4065 let Some(room) = room else {
4066 return anyhow::Ok(true);
4067 };
4068
4069 room.update(cx, |room, _| room.room_update_completed())?
4070 .await;
4071
4072 let task = room.update(cx, |room, cx| {
4073 if let Some((project, host)) = room.most_active_project(cx) {
4074 return Some(join_remote_project(project, host, app_state.clone(), cx));
4075 }
4076
4077 None
4078 })?;
4079 if let Some(task) = task {
4080 task.await?;
4081 return anyhow::Ok(true);
4082 }
4083 anyhow::Ok(false)
4084}
4085
4086pub fn join_channel(
4087 channel_id: u64,
4088 app_state: Arc<AppState>,
4089 requesting_window: Option<WindowHandle<Workspace>>,
4090 cx: &mut AppContext,
4091) -> Task<Result<()>> {
4092 let active_call = ActiveCall::global(cx);
4093 cx.spawn(|mut cx| async move {
4094 let result = join_channel_internal(
4095 channel_id,
4096 &app_state,
4097 requesting_window,
4098 &active_call,
4099 &mut cx,
4100 )
4101 .await;
4102
4103 // join channel succeeded, and opened a window
4104 if matches!(result, Ok(true)) {
4105 return anyhow::Ok(());
4106 }
4107
4108 if requesting_window.is_some() {
4109 return anyhow::Ok(());
4110 }
4111
4112 // find an existing workspace to focus and show call controls
4113 let mut active_window = activate_any_workspace_window(&mut cx);
4114 if active_window.is_none() {
4115 // no open workspaces, make one to show the error in (blergh)
4116 cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4117 .await?;
4118 }
4119
4120 active_window = activate_any_workspace_window(&mut cx);
4121 let Some(active_window) = active_window else {
4122 return anyhow::Ok(());
4123 };
4124
4125 if let Err(err) = result {
4126 active_window
4127 .update(&mut cx, |_, cx| {
4128 cx.prompt(
4129 PromptLevel::Critical,
4130 &format!("Failed to join channel: {}", err),
4131 &["Ok"],
4132 )
4133 })?
4134 .await
4135 .ok();
4136 }
4137
4138 // return ok, we showed the error to the user.
4139 return anyhow::Ok(());
4140 })
4141}
4142
4143pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4144 cx.update(|cx| {
4145 for window in cx.windows() {
4146 let is_workspace = window.downcast::<Workspace>().is_some();
4147 if is_workspace {
4148 window.update(cx, |_, cx| cx.activate_window()).ok();
4149 return Some(window);
4150 }
4151 }
4152 None
4153 })
4154 .ok()
4155 .flatten()
4156}
4157
4158#[allow(clippy::type_complexity)]
4159pub fn open_paths(
4160 abs_paths: &[PathBuf],
4161 app_state: &Arc<AppState>,
4162 requesting_window: Option<WindowHandle<Workspace>>,
4163 cx: &mut AppContext,
4164) -> Task<
4165 anyhow::Result<(
4166 WindowHandle<Workspace>,
4167 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4168 )>,
4169> {
4170 let app_state = app_state.clone();
4171 let abs_paths = abs_paths.to_vec();
4172 // Open paths in existing workspace if possible
4173 let existing = activate_workspace_for_project(cx, {
4174 let abs_paths = abs_paths.clone();
4175 move |project, cx| project.contains_paths(&abs_paths, cx)
4176 });
4177 cx.spawn(move |mut cx| async move {
4178 if let Some(existing) = existing {
4179 // // Ok((
4180 // existing.clone(),
4181 // cx.update_window_root(&existing, |workspace, cx| {
4182 // workspace.open_paths(abs_paths, true, cx)
4183 // })?
4184 // .await,
4185 // ))
4186 todo!()
4187 } else {
4188 cx.update(move |cx| {
4189 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4190 })?
4191 .await
4192 }
4193 })
4194}
4195
4196pub fn open_new(
4197 app_state: &Arc<AppState>,
4198 cx: &mut AppContext,
4199 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4200) -> Task<()> {
4201 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4202 cx.spawn(|mut cx| async move {
4203 if let Some((workspace, opened_paths)) = task.await.log_err() {
4204 workspace
4205 .update(&mut cx, |workspace, cx| {
4206 if opened_paths.is_empty() {
4207 init(workspace, cx)
4208 }
4209 })
4210 .log_err();
4211 }
4212 })
4213}
4214
4215pub fn create_and_open_local_file(
4216 path: &'static Path,
4217 cx: &mut ViewContext<Workspace>,
4218 default_content: impl 'static + Send + FnOnce() -> Rope,
4219) -> Task<Result<Box<dyn ItemHandle>>> {
4220 cx.spawn(|workspace, mut cx| async move {
4221 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4222 if !fs.is_file(path).await {
4223 fs.create_file(path, Default::default()).await?;
4224 fs.save(path, &default_content(), Default::default())
4225 .await?;
4226 }
4227
4228 let mut items = workspace
4229 .update(&mut cx, |workspace, cx| {
4230 workspace.with_local_workspace(cx, |workspace, cx| {
4231 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4232 })
4233 })?
4234 .await?
4235 .await;
4236
4237 let item = items.pop().flatten();
4238 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4239 })
4240}
4241
4242pub fn join_remote_project(
4243 project_id: u64,
4244 follow_user_id: u64,
4245 app_state: Arc<AppState>,
4246 cx: &mut AppContext,
4247) -> Task<Result<()>> {
4248 let windows = cx.windows();
4249 cx.spawn(|mut cx| async move {
4250 let existing_workspace = windows.into_iter().find_map(|window| {
4251 window.downcast::<Workspace>().and_then(|window| {
4252 window
4253 .update(&mut cx, |workspace, cx| {
4254 if workspace.project().read(cx).remote_id() == Some(project_id) {
4255 Some(window)
4256 } else {
4257 None
4258 }
4259 })
4260 .unwrap_or(None)
4261 })
4262 });
4263
4264 let workspace = if let Some(existing_workspace) = existing_workspace {
4265 existing_workspace
4266 } else {
4267 let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4268 let room = active_call
4269 .read_with(&cx, |call, _| call.room().cloned())?
4270 .ok_or_else(|| anyhow!("not in a call"))?;
4271 let project = room
4272 .update(&mut cx, |room, cx| {
4273 room.join_project(
4274 project_id,
4275 app_state.languages.clone(),
4276 app_state.fs.clone(),
4277 cx,
4278 )
4279 })?
4280 .await?;
4281
4282 let window_bounds_override = window_bounds_env_override(&cx);
4283 cx.update(|cx| {
4284 let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4285 cx.open_window(options, |cx| {
4286 cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4287 })
4288 })?
4289 };
4290
4291 workspace.update(&mut cx, |workspace, cx| {
4292 cx.activate(true);
4293 cx.activate_window();
4294
4295 if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4296 let follow_peer_id = room
4297 .read(cx)
4298 .remote_participants()
4299 .iter()
4300 .find(|(_, participant)| participant.user.id == follow_user_id)
4301 .map(|(_, p)| p.peer_id)
4302 .or_else(|| {
4303 // If we couldn't follow the given user, follow the host instead.
4304 let collaborator = workspace
4305 .project()
4306 .read(cx)
4307 .collaborators()
4308 .values()
4309 .find(|collaborator| collaborator.replica_id == 0)?;
4310 Some(collaborator.peer_id)
4311 });
4312
4313 if let Some(follow_peer_id) = follow_peer_id {
4314 workspace
4315 .follow(follow_peer_id, cx)
4316 .map(|follow| follow.detach_and_log_err(cx));
4317 }
4318 }
4319 })?;
4320
4321 anyhow::Ok(())
4322 })
4323}
4324
4325pub fn restart(_: &Restart, cx: &mut AppContext) {
4326 let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4327 let mut workspace_windows = cx
4328 .windows()
4329 .into_iter()
4330 .filter_map(|window| window.downcast::<Workspace>())
4331 .collect::<Vec<_>>();
4332
4333 // If multiple windows have unsaved changes, and need a save prompt,
4334 // prompt in the active window before switching to a different window.
4335 workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4336
4337 let mut prompt = None;
4338 if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4339 prompt = window
4340 .update(cx, |_, cx| {
4341 cx.prompt(
4342 PromptLevel::Info,
4343 "Are you sure you want to restart?",
4344 &["Restart", "Cancel"],
4345 )
4346 })
4347 .ok();
4348 }
4349
4350 cx.spawn(|mut cx| async move {
4351 if let Some(mut prompt) = prompt {
4352 let answer = prompt.await?;
4353 if answer != 0 {
4354 return Ok(());
4355 }
4356 }
4357
4358 // If the user cancels any save prompt, then keep the app open.
4359 for window in workspace_windows {
4360 if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4361 workspace.prepare_to_close(true, cx)
4362 }) {
4363 if !should_close.await? {
4364 return Ok(());
4365 }
4366 }
4367 }
4368
4369 cx.update(|cx| cx.restart())
4370 })
4371 .detach_and_log_err(cx);
4372}
4373
4374fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4375 let mut parts = value.split(',');
4376 let x: usize = parts.next()?.parse().ok()?;
4377 let y: usize = parts.next()?.parse().ok()?;
4378 Some(point((x as f64).into(), (y as f64).into()))
4379}
4380
4381fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4382 let mut parts = value.split(',');
4383 let width: usize = parts.next()?.parse().ok()?;
4384 let height: usize = parts.next()?.parse().ok()?;
4385 Some(size((width as f64).into(), (height as f64).into()))
4386}
4387
4388#[cfg(test)]
4389mod tests {
4390 use super::*;
4391 use crate::item::{
4392 test::{TestItem, TestProjectItem},
4393 ItemEvent,
4394 };
4395 use fs::FakeFs;
4396 use gpui::TestAppContext;
4397 use project::{Project, ProjectEntryId};
4398 use serde_json::json;
4399 use settings::SettingsStore;
4400 use std::{cell::RefCell, rc::Rc};
4401
4402 #[gpui::test]
4403 async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4404 init_test(cx);
4405
4406 let fs = FakeFs::new(cx.executor());
4407 let project = Project::test(fs, [], cx).await;
4408 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4409
4410 // Adding an item with no ambiguity renders the tab without detail.
4411 let item1 = cx.build_view(|cx| {
4412 let mut item = TestItem::new(cx);
4413 item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4414 item
4415 });
4416 workspace.update(cx, |workspace, cx| {
4417 workspace.add_item(Box::new(item1.clone()), cx);
4418 });
4419 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4420
4421 // Adding an item that creates ambiguity increases the level of detail on
4422 // both tabs.
4423 let item2 = cx.build_view(|cx| {
4424 let mut item = TestItem::new(cx);
4425 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4426 item
4427 });
4428 workspace.update(cx, |workspace, cx| {
4429 workspace.add_item(Box::new(item2.clone()), cx);
4430 });
4431 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4432 item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4433
4434 // Adding an item that creates ambiguity increases the level of detail only
4435 // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4436 // we stop at the highest detail available.
4437 let item3 = cx.build_view(|cx| {
4438 let mut item = TestItem::new(cx);
4439 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4440 item
4441 });
4442 workspace.update(cx, |workspace, cx| {
4443 workspace.add_item(Box::new(item3.clone()), cx);
4444 });
4445 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4446 item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4447 item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4448 }
4449
4450 #[gpui::test]
4451 async fn test_tracking_active_path(cx: &mut TestAppContext) {
4452 init_test(cx);
4453
4454 let fs = FakeFs::new(cx.executor());
4455 fs.insert_tree(
4456 "/root1",
4457 json!({
4458 "one.txt": "",
4459 "two.txt": "",
4460 }),
4461 )
4462 .await;
4463 fs.insert_tree(
4464 "/root2",
4465 json!({
4466 "three.txt": "",
4467 }),
4468 )
4469 .await;
4470
4471 let project = Project::test(fs, ["root1".as_ref()], cx).await;
4472 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4473 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4474 let worktree_id = project.read_with(cx, |project, cx| {
4475 project.worktrees().next().unwrap().read(cx).id()
4476 });
4477
4478 let item1 = cx.build_view(|cx| {
4479 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4480 });
4481 let item2 = cx.build_view(|cx| {
4482 TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4483 });
4484
4485 // Add an item to an empty pane
4486 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4487 project.read_with(cx, |project, cx| {
4488 assert_eq!(
4489 project.active_entry(),
4490 project
4491 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4492 .map(|e| e.id)
4493 );
4494 });
4495 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
4496
4497 // Add a second item to a non-empty pane
4498 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4499 assert_eq!(cx.window_title().as_deref(), Some("two.txt β root1"));
4500 project.read_with(cx, |project, cx| {
4501 assert_eq!(
4502 project.active_entry(),
4503 project
4504 .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4505 .map(|e| e.id)
4506 );
4507 });
4508
4509 // Close the active item
4510 pane.update(cx, |pane, cx| {
4511 pane.close_active_item(&Default::default(), cx).unwrap()
4512 })
4513 .await
4514 .unwrap();
4515 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
4516 project.read_with(cx, |project, cx| {
4517 assert_eq!(
4518 project.active_entry(),
4519 project
4520 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4521 .map(|e| e.id)
4522 );
4523 });
4524
4525 // Add a project folder
4526 project
4527 .update(cx, |project, cx| {
4528 project.find_or_create_local_worktree("/root2", true, cx)
4529 })
4530 .await
4531 .unwrap();
4532 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1, root2"));
4533
4534 // Remove a project folder
4535 project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4536 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root2"));
4537 }
4538
4539 #[gpui::test]
4540 async fn test_close_window(cx: &mut TestAppContext) {
4541 init_test(cx);
4542
4543 let fs = FakeFs::new(cx.executor());
4544 fs.insert_tree("/root", json!({ "one": "" })).await;
4545
4546 let project = Project::test(fs, ["root".as_ref()], cx).await;
4547 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4548
4549 // When there are no dirty items, there's nothing to do.
4550 let item1 = cx.build_view(|cx| TestItem::new(cx));
4551 workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4552 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4553 assert!(task.await.unwrap());
4554
4555 // When there are dirty untitled items, prompt to save each one. If the user
4556 // cancels any prompt, then abort.
4557 let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4558 let item3 = cx.build_view(|cx| {
4559 TestItem::new(cx)
4560 .with_dirty(true)
4561 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4562 });
4563 workspace.update(cx, |w, cx| {
4564 w.add_item(Box::new(item2.clone()), cx);
4565 w.add_item(Box::new(item3.clone()), cx);
4566 });
4567 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4568 cx.executor().run_until_parked();
4569 cx.simulate_prompt_answer(2); // cancel save all
4570 cx.executor().run_until_parked();
4571 cx.simulate_prompt_answer(2); // cancel save all
4572 cx.executor().run_until_parked();
4573 assert!(!cx.has_pending_prompt());
4574 assert!(!task.await.unwrap());
4575 }
4576
4577 #[gpui::test]
4578 async fn test_close_pane_items(cx: &mut TestAppContext) {
4579 init_test(cx);
4580
4581 let fs = FakeFs::new(cx.executor());
4582
4583 let project = Project::test(fs, None, cx).await;
4584 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4585
4586 let item1 = cx.build_view(|cx| {
4587 TestItem::new(cx)
4588 .with_dirty(true)
4589 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4590 });
4591 let item2 = cx.build_view(|cx| {
4592 TestItem::new(cx)
4593 .with_dirty(true)
4594 .with_conflict(true)
4595 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4596 });
4597 let item3 = cx.build_view(|cx| {
4598 TestItem::new(cx)
4599 .with_dirty(true)
4600 .with_conflict(true)
4601 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4602 });
4603 let item4 = cx.build_view(|cx| {
4604 TestItem::new(cx)
4605 .with_dirty(true)
4606 .with_project_items(&[TestProjectItem::new_untitled(cx)])
4607 });
4608 let pane = workspace.update(cx, |workspace, cx| {
4609 workspace.add_item(Box::new(item1.clone()), cx);
4610 workspace.add_item(Box::new(item2.clone()), cx);
4611 workspace.add_item(Box::new(item3.clone()), cx);
4612 workspace.add_item(Box::new(item4.clone()), cx);
4613 workspace.active_pane().clone()
4614 });
4615
4616 let close_items = pane.update(cx, |pane, cx| {
4617 pane.activate_item(1, true, true, cx);
4618 assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4619 let item1_id = item1.item_id();
4620 let item3_id = item3.item_id();
4621 let item4_id = item4.item_id();
4622 pane.close_items(cx, SaveIntent::Close, move |id| {
4623 [item1_id, item3_id, item4_id].contains(&id)
4624 })
4625 });
4626 cx.executor().run_until_parked();
4627
4628 assert!(cx.has_pending_prompt());
4629 // Ignore "Save all" prompt
4630 cx.simulate_prompt_answer(2);
4631 cx.executor().run_until_parked();
4632 // There's a prompt to save item 1.
4633 pane.update(cx, |pane, _| {
4634 assert_eq!(pane.items_len(), 4);
4635 assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4636 });
4637 // Confirm saving item 1.
4638 cx.simulate_prompt_answer(0);
4639 cx.executor().run_until_parked();
4640
4641 // Item 1 is saved. There's a prompt to save item 3.
4642 pane.update(cx, |pane, cx| {
4643 assert_eq!(item1.read(cx).save_count, 1);
4644 assert_eq!(item1.read(cx).save_as_count, 0);
4645 assert_eq!(item1.read(cx).reload_count, 0);
4646 assert_eq!(pane.items_len(), 3);
4647 assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4648 });
4649 assert!(cx.has_pending_prompt());
4650
4651 // Cancel saving item 3.
4652 cx.simulate_prompt_answer(1);
4653 cx.executor().run_until_parked();
4654
4655 // Item 3 is reloaded. There's a prompt to save item 4.
4656 pane.update(cx, |pane, cx| {
4657 assert_eq!(item3.read(cx).save_count, 0);
4658 assert_eq!(item3.read(cx).save_as_count, 0);
4659 assert_eq!(item3.read(cx).reload_count, 1);
4660 assert_eq!(pane.items_len(), 2);
4661 assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4662 });
4663 assert!(cx.has_pending_prompt());
4664
4665 // Confirm saving item 4.
4666 cx.simulate_prompt_answer(0);
4667 cx.executor().run_until_parked();
4668
4669 // There's a prompt for a path for item 4.
4670 cx.simulate_new_path_selection(|_| Some(Default::default()));
4671 close_items.await.unwrap();
4672
4673 // The requested items are closed.
4674 pane.update(cx, |pane, cx| {
4675 assert_eq!(item4.read(cx).save_count, 0);
4676 assert_eq!(item4.read(cx).save_as_count, 1);
4677 assert_eq!(item4.read(cx).reload_count, 0);
4678 assert_eq!(pane.items_len(), 1);
4679 assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4680 });
4681 }
4682
4683 #[gpui::test]
4684 async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4685 init_test(cx);
4686
4687 let fs = FakeFs::new(cx.executor());
4688 let project = Project::test(fs, [], cx).await;
4689 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4690
4691 // Create several workspace items with single project entries, and two
4692 // workspace items with multiple project entries.
4693 let single_entry_items = (0..=4)
4694 .map(|project_entry_id| {
4695 cx.build_view(|cx| {
4696 TestItem::new(cx)
4697 .with_dirty(true)
4698 .with_project_items(&[TestProjectItem::new(
4699 project_entry_id,
4700 &format!("{project_entry_id}.txt"),
4701 cx,
4702 )])
4703 })
4704 })
4705 .collect::<Vec<_>>();
4706 let item_2_3 = cx.build_view(|cx| {
4707 TestItem::new(cx)
4708 .with_dirty(true)
4709 .with_singleton(false)
4710 .with_project_items(&[
4711 single_entry_items[2].read(cx).project_items[0].clone(),
4712 single_entry_items[3].read(cx).project_items[0].clone(),
4713 ])
4714 });
4715 let item_3_4 = cx.build_view(|cx| {
4716 TestItem::new(cx)
4717 .with_dirty(true)
4718 .with_singleton(false)
4719 .with_project_items(&[
4720 single_entry_items[3].read(cx).project_items[0].clone(),
4721 single_entry_items[4].read(cx).project_items[0].clone(),
4722 ])
4723 });
4724
4725 // Create two panes that contain the following project entries:
4726 // left pane:
4727 // multi-entry items: (2, 3)
4728 // single-entry items: 0, 1, 2, 3, 4
4729 // right pane:
4730 // single-entry items: 1
4731 // multi-entry items: (3, 4)
4732 let left_pane = workspace.update(cx, |workspace, cx| {
4733 let left_pane = workspace.active_pane().clone();
4734 workspace.add_item(Box::new(item_2_3.clone()), cx);
4735 for item in single_entry_items {
4736 workspace.add_item(Box::new(item), cx);
4737 }
4738 left_pane.update(cx, |pane, cx| {
4739 pane.activate_item(2, true, true, cx);
4740 });
4741
4742 let right_pane = workspace
4743 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4744 .unwrap();
4745
4746 right_pane.update(cx, |pane, cx| {
4747 pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4748 });
4749
4750 left_pane
4751 });
4752
4753 cx.focus_view(&left_pane);
4754
4755 // When closing all of the items in the left pane, we should be prompted twice:
4756 // once for project entry 0, and once for project entry 2. Project entries 1,
4757 // 3, and 4 are all still open in the other paten. After those two
4758 // prompts, the task should complete.
4759
4760 let close = left_pane.update(cx, |pane, cx| {
4761 pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4762 });
4763 cx.executor().run_until_parked();
4764
4765 // Discard "Save all" prompt
4766 cx.simulate_prompt_answer(2);
4767
4768 cx.executor().run_until_parked();
4769 left_pane.update(cx, |pane, cx| {
4770 assert_eq!(
4771 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4772 &[ProjectEntryId::from_proto(0)]
4773 );
4774 });
4775 cx.simulate_prompt_answer(0);
4776
4777 cx.executor().run_until_parked();
4778 left_pane.update(cx, |pane, cx| {
4779 assert_eq!(
4780 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4781 &[ProjectEntryId::from_proto(2)]
4782 );
4783 });
4784 cx.simulate_prompt_answer(0);
4785
4786 cx.executor().run_until_parked();
4787 close.await.unwrap();
4788 left_pane.update(cx, |pane, _| {
4789 assert_eq!(pane.items_len(), 0);
4790 });
4791 }
4792
4793 #[gpui::test]
4794 async fn test_autosave(cx: &mut gpui::TestAppContext) {
4795 init_test(cx);
4796
4797 let fs = FakeFs::new(cx.executor());
4798 let project = Project::test(fs, [], cx).await;
4799 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4800 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4801
4802 let item = cx.build_view(|cx| {
4803 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4804 });
4805 let item_id = item.entity_id();
4806 workspace.update(cx, |workspace, cx| {
4807 workspace.add_item(Box::new(item.clone()), cx);
4808 });
4809
4810 // Autosave on window change.
4811 item.update(cx, |item, cx| {
4812 cx.update_global(|settings: &mut SettingsStore, cx| {
4813 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4814 settings.autosave = Some(AutosaveSetting::OnWindowChange);
4815 })
4816 });
4817 item.is_dirty = true;
4818 });
4819
4820 // Deactivating the window saves the file.
4821 cx.simulate_deactivation();
4822 cx.executor().run_until_parked();
4823 item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4824
4825 // Autosave on focus change.
4826 item.update(cx, |item, cx| {
4827 cx.focus_self();
4828 cx.update_global(|settings: &mut SettingsStore, cx| {
4829 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4830 settings.autosave = Some(AutosaveSetting::OnFocusChange);
4831 })
4832 });
4833 item.is_dirty = true;
4834 });
4835
4836 // Blurring the item saves the file.
4837 item.update(cx, |_, cx| cx.blur());
4838 cx.executor().run_until_parked();
4839 item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4840
4841 // Deactivating the window still saves the file.
4842 cx.simulate_activation();
4843 item.update(cx, |item, cx| {
4844 cx.focus_self();
4845 item.is_dirty = true;
4846 });
4847 cx.simulate_deactivation();
4848
4849 cx.executor().run_until_parked();
4850 item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4851
4852 // Autosave after delay.
4853 item.update(cx, |item, cx| {
4854 cx.update_global(|settings: &mut SettingsStore, cx| {
4855 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4856 settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4857 })
4858 });
4859 item.is_dirty = true;
4860 cx.emit(ItemEvent::Edit);
4861 });
4862
4863 // Delay hasn't fully expired, so the file is still dirty and unsaved.
4864 cx.executor().advance_clock(Duration::from_millis(250));
4865 item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4866
4867 // After delay expires, the file is saved.
4868 cx.executor().advance_clock(Duration::from_millis(250));
4869 item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4870
4871 // Autosave on focus change, ensuring closing the tab counts as such.
4872 item.update(cx, |item, cx| {
4873 cx.update_global(|settings: &mut SettingsStore, cx| {
4874 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4875 settings.autosave = Some(AutosaveSetting::OnFocusChange);
4876 })
4877 });
4878 item.is_dirty = true;
4879 });
4880
4881 pane.update(cx, |pane, cx| {
4882 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4883 })
4884 .await
4885 .unwrap();
4886 assert!(!cx.has_pending_prompt());
4887 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4888
4889 // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4890 workspace.update(cx, |workspace, cx| {
4891 workspace.add_item(Box::new(item.clone()), cx);
4892 });
4893 item.update(cx, |item, cx| {
4894 item.project_items[0].update(cx, |item, _| {
4895 item.entry_id = None;
4896 });
4897 item.is_dirty = true;
4898 cx.blur();
4899 });
4900 cx.executor().run_until_parked();
4901 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4902
4903 // Ensure autosave is prevented for deleted files also when closing the buffer.
4904 let _close_items = pane.update(cx, |pane, cx| {
4905 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4906 });
4907 cx.executor().run_until_parked();
4908 assert!(cx.has_pending_prompt());
4909 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4910 }
4911
4912 #[gpui::test]
4913 async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4914 init_test(cx);
4915
4916 let fs = FakeFs::new(cx.executor());
4917
4918 let project = Project::test(fs, [], cx).await;
4919 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4920
4921 let item = cx.build_view(|cx| {
4922 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4923 });
4924 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4925 let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4926 let toolbar_notify_count = Rc::new(RefCell::new(0));
4927
4928 workspace.update(cx, |workspace, cx| {
4929 workspace.add_item(Box::new(item.clone()), cx);
4930 let toolbar_notification_count = toolbar_notify_count.clone();
4931 cx.observe(&toolbar, move |_, _, _| {
4932 *toolbar_notification_count.borrow_mut() += 1
4933 })
4934 .detach();
4935 });
4936
4937 pane.update(cx, |pane, _| {
4938 assert!(!pane.can_navigate_backward());
4939 assert!(!pane.can_navigate_forward());
4940 });
4941
4942 item.update(cx, |item, cx| {
4943 item.set_state("one".to_string(), cx);
4944 });
4945
4946 // Toolbar must be notified to re-render the navigation buttons
4947 assert_eq!(*toolbar_notify_count.borrow(), 1);
4948
4949 pane.update(cx, |pane, _| {
4950 assert!(pane.can_navigate_backward());
4951 assert!(!pane.can_navigate_forward());
4952 });
4953
4954 workspace
4955 .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4956 .await
4957 .unwrap();
4958
4959 assert_eq!(*toolbar_notify_count.borrow(), 2);
4960 pane.update(cx, |pane, _| {
4961 assert!(!pane.can_navigate_backward());
4962 assert!(pane.can_navigate_forward());
4963 });
4964 }
4965
4966 // #[gpui::test]
4967 // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4968 // init_test(cx);
4969 // let fs = FakeFs::new(cx.executor());
4970
4971 // let project = Project::test(fs, [], cx).await;
4972 // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4973 // let workspace = window.root(cx);
4974
4975 // let panel = workspace.update(cx, |workspace, cx| {
4976 // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
4977 // workspace.add_panel(panel.clone(), cx);
4978
4979 // workspace
4980 // .right_dock()
4981 // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4982
4983 // panel
4984 // });
4985
4986 // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4987 // pane.update(cx, |pane, cx| {
4988 // let item = cx.build_view(|_| TestItem::new(cx));
4989 // pane.add_item(Box::new(item), true, true, None, cx);
4990 // });
4991
4992 // // Transfer focus from center to panel
4993 // workspace.update(cx, |workspace, cx| {
4994 // workspace.toggle_panel_focus::<TestPanel>(cx);
4995 // });
4996
4997 // workspace.update(cx, |workspace, cx| {
4998 // assert!(workspace.right_dock().read(cx).is_open());
4999 // assert!(!panel.is_zoomed(cx));
5000 // assert!(panel.has_focus(cx));
5001 // });
5002
5003 // // Transfer focus from panel to center
5004 // workspace.update(cx, |workspace, cx| {
5005 // workspace.toggle_panel_focus::<TestPanel>(cx);
5006 // });
5007
5008 // workspace.update(cx, |workspace, cx| {
5009 // assert!(workspace.right_dock().read(cx).is_open());
5010 // assert!(!panel.is_zoomed(cx));
5011 // assert!(!panel.has_focus(cx));
5012 // });
5013
5014 // // Close the dock
5015 // workspace.update(cx, |workspace, cx| {
5016 // workspace.toggle_dock(DockPosition::Right, cx);
5017 // });
5018
5019 // workspace.update(cx, |workspace, cx| {
5020 // assert!(!workspace.right_dock().read(cx).is_open());
5021 // assert!(!panel.is_zoomed(cx));
5022 // assert!(!panel.has_focus(cx));
5023 // });
5024
5025 // // Open the dock
5026 // workspace.update(cx, |workspace, cx| {
5027 // workspace.toggle_dock(DockPosition::Right, cx);
5028 // });
5029
5030 // workspace.update(cx, |workspace, cx| {
5031 // assert!(workspace.right_dock().read(cx).is_open());
5032 // assert!(!panel.is_zoomed(cx));
5033 // assert!(panel.has_focus(cx));
5034 // });
5035
5036 // // Focus and zoom panel
5037 // panel.update(cx, |panel, cx| {
5038 // cx.focus_self();
5039 // panel.set_zoomed(true, cx)
5040 // });
5041
5042 // workspace.update(cx, |workspace, cx| {
5043 // assert!(workspace.right_dock().read(cx).is_open());
5044 // assert!(panel.is_zoomed(cx));
5045 // assert!(panel.has_focus(cx));
5046 // });
5047
5048 // // Transfer focus to the center closes the dock
5049 // workspace.update(cx, |workspace, cx| {
5050 // workspace.toggle_panel_focus::<TestPanel>(cx);
5051 // });
5052
5053 // workspace.update(cx, |workspace, cx| {
5054 // assert!(!workspace.right_dock().read(cx).is_open());
5055 // assert!(panel.is_zoomed(cx));
5056 // assert!(!panel.has_focus(cx));
5057 // });
5058
5059 // // Transferring focus back to the panel keeps it zoomed
5060 // workspace.update(cx, |workspace, cx| {
5061 // workspace.toggle_panel_focus::<TestPanel>(cx);
5062 // });
5063
5064 // workspace.update(cx, |workspace, cx| {
5065 // assert!(workspace.right_dock().read(cx).is_open());
5066 // assert!(panel.is_zoomed(cx));
5067 // assert!(panel.has_focus(cx));
5068 // });
5069
5070 // // Close the dock while it is zoomed
5071 // workspace.update(cx, |workspace, cx| {
5072 // workspace.toggle_dock(DockPosition::Right, cx)
5073 // });
5074
5075 // workspace.update(cx, |workspace, cx| {
5076 // assert!(!workspace.right_dock().read(cx).is_open());
5077 // assert!(panel.is_zoomed(cx));
5078 // assert!(workspace.zoomed.is_none());
5079 // assert!(!panel.has_focus(cx));
5080 // });
5081
5082 // // Opening the dock, when it's zoomed, retains focus
5083 // workspace.update(cx, |workspace, cx| {
5084 // workspace.toggle_dock(DockPosition::Right, cx)
5085 // });
5086
5087 // workspace.update(cx, |workspace, cx| {
5088 // assert!(workspace.right_dock().read(cx).is_open());
5089 // assert!(panel.is_zoomed(cx));
5090 // assert!(workspace.zoomed.is_some());
5091 // assert!(panel.has_focus(cx));
5092 // });
5093
5094 // // Unzoom and close the panel, zoom the active pane.
5095 // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5096 // workspace.update(cx, |workspace, cx| {
5097 // workspace.toggle_dock(DockPosition::Right, cx)
5098 // });
5099 // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5100
5101 // // Opening a dock unzooms the pane.
5102 // workspace.update(cx, |workspace, cx| {
5103 // workspace.toggle_dock(DockPosition::Right, cx)
5104 // });
5105 // workspace.update(cx, |workspace, cx| {
5106 // let pane = pane.read(cx);
5107 // assert!(!pane.is_zoomed());
5108 // assert!(!pane.has_focus());
5109 // assert!(workspace.right_dock().read(cx).is_open());
5110 // assert!(workspace.zoomed.is_none());
5111 // });
5112 // }
5113
5114 // #[gpui::test]
5115 // async fn test_panels(cx: &mut gpui::TestAppContext) {
5116 // init_test(cx);
5117 // let fs = FakeFs::new(cx.executor());
5118
5119 // let project = Project::test(fs, [], cx).await;
5120 // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5121 // let workspace = window.root(cx);
5122
5123 // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5124 // // Add panel_1 on the left, panel_2 on the right.
5125 // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5126 // workspace.add_panel(panel_1.clone(), cx);
5127 // workspace
5128 // .left_dock()
5129 // .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5130 // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5131 // workspace.add_panel(panel_2.clone(), cx);
5132 // workspace
5133 // .right_dock()
5134 // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5135
5136 // let left_dock = workspace.left_dock();
5137 // assert_eq!(
5138 // left_dock.read(cx).visible_panel().unwrap().id(),
5139 // panel_1.id()
5140 // );
5141 // assert_eq!(
5142 // left_dock.read(cx).active_panel_size(cx).unwrap(),
5143 // panel_1.size(cx)
5144 // );
5145
5146 // left_dock.update(cx, |left_dock, cx| {
5147 // left_dock.resize_active_panel(Some(1337.), cx)
5148 // });
5149 // assert_eq!(
5150 // workspace
5151 // .right_dock()
5152 // .read(cx)
5153 // .visible_panel()
5154 // .unwrap()
5155 // .id(),
5156 // panel_2.id()
5157 // );
5158
5159 // (panel_1, panel_2)
5160 // });
5161
5162 // // Move panel_1 to the right
5163 // panel_1.update(cx, |panel_1, cx| {
5164 // panel_1.set_position(DockPosition::Right, cx)
5165 // });
5166
5167 // workspace.update(cx, |workspace, cx| {
5168 // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5169 // // Since it was the only panel on the left, the left dock should now be closed.
5170 // assert!(!workspace.left_dock().read(cx).is_open());
5171 // assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5172 // let right_dock = workspace.right_dock();
5173 // assert_eq!(
5174 // right_dock.read(cx).visible_panel().unwrap().id(),
5175 // panel_1.id()
5176 // );
5177 // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5178
5179 // // Now we move panel_2Β to the left
5180 // panel_2.set_position(DockPosition::Left, cx);
5181 // });
5182
5183 // workspace.update(cx, |workspace, cx| {
5184 // // Since panel_2 was not visible on the right, we don't open the left dock.
5185 // assert!(!workspace.left_dock().read(cx).is_open());
5186 // // And the right dock is unaffected in it's displaying of panel_1
5187 // assert!(workspace.right_dock().read(cx).is_open());
5188 // assert_eq!(
5189 // workspace
5190 // .right_dock()
5191 // .read(cx)
5192 // .visible_panel()
5193 // .unwrap()
5194 // .id(),
5195 // panel_1.id()
5196 // );
5197 // });
5198
5199 // // Move panel_1 back to the left
5200 // panel_1.update(cx, |panel_1, cx| {
5201 // panel_1.set_position(DockPosition::Left, cx)
5202 // });
5203
5204 // workspace.update(cx, |workspace, cx| {
5205 // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5206 // let left_dock = workspace.left_dock();
5207 // assert!(left_dock.read(cx).is_open());
5208 // assert_eq!(
5209 // left_dock.read(cx).visible_panel().unwrap().id(),
5210 // panel_1.id()
5211 // );
5212 // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5213 // // And right the dock should be closed as it no longer has any panels.
5214 // assert!(!workspace.right_dock().read(cx).is_open());
5215
5216 // // Now we move panel_1 to the bottom
5217 // panel_1.set_position(DockPosition::Bottom, cx);
5218 // });
5219
5220 // workspace.update(cx, |workspace, cx| {
5221 // // Since panel_1 was visible on the left, we close the left dock.
5222 // assert!(!workspace.left_dock().read(cx).is_open());
5223 // // The bottom dock is sized based on the panel's default size,
5224 // // since the panel orientation changed from vertical to horizontal.
5225 // let bottom_dock = workspace.bottom_dock();
5226 // assert_eq!(
5227 // bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5228 // panel_1.size(cx),
5229 // );
5230 // // Close bottom dock and move panel_1 back to the left.
5231 // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5232 // panel_1.set_position(DockPosition::Left, cx);
5233 // });
5234
5235 // // Emit activated event on panel 1
5236 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5237
5238 // // Now the left dock is open and panel_1 is active and focused.
5239 // workspace.update(cx, |workspace, cx| {
5240 // let left_dock = workspace.left_dock();
5241 // assert!(left_dock.read(cx).is_open());
5242 // assert_eq!(
5243 // left_dock.read(cx).visible_panel().unwrap().id(),
5244 // panel_1.id()
5245 // );
5246 // assert!(panel_1.is_focused(cx));
5247 // });
5248
5249 // // Emit closed event on panel 2, which is not active
5250 // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5251
5252 // // Wo don't close the left dock, because panel_2 wasn't the active panel
5253 // workspace.update(cx, |workspace, cx| {
5254 // let left_dock = workspace.left_dock();
5255 // assert!(left_dock.read(cx).is_open());
5256 // assert_eq!(
5257 // left_dock.read(cx).visible_panel().unwrap().id(),
5258 // panel_1.id()
5259 // );
5260 // });
5261
5262 // // Emitting a ZoomIn event shows the panel as zoomed.
5263 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5264 // workspace.update(cx, |workspace, _| {
5265 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5266 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5267 // });
5268
5269 // // Move panel to another dock while it is zoomed
5270 // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5271 // workspace.update(cx, |workspace, _| {
5272 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5273 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5274 // });
5275
5276 // // If focus is transferred to another view that's not a panel or another pane, we still show
5277 // // the panel as zoomed.
5278 // let focus_receiver = cx.build_view(|_| EmptyView);
5279 // focus_receiver.update(cx, |_, cx| cx.focus_self());
5280 // workspace.update(cx, |workspace, _| {
5281 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5282 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5283 // });
5284
5285 // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5286 // workspace.update(cx, |_, cx| cx.focus_self());
5287 // workspace.update(cx, |workspace, _| {
5288 // assert_eq!(workspace.zoomed, None);
5289 // assert_eq!(workspace.zoomed_position, None);
5290 // });
5291
5292 // // If focus is transferred again to another view that's not a panel or a pane, we won't
5293 // // show the panel as zoomed because it wasn't zoomed before.
5294 // focus_receiver.update(cx, |_, cx| cx.focus_self());
5295 // workspace.update(cx, |workspace, _| {
5296 // assert_eq!(workspace.zoomed, None);
5297 // assert_eq!(workspace.zoomed_position, None);
5298 // });
5299
5300 // // When focus is transferred back to the panel, it is zoomed again.
5301 // panel_1.update(cx, |_, cx| cx.focus_self());
5302 // workspace.update(cx, |workspace, _| {
5303 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5304 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5305 // });
5306
5307 // // Emitting a ZoomOut event unzooms the panel.
5308 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5309 // workspace.update(cx, |workspace, _| {
5310 // assert_eq!(workspace.zoomed, None);
5311 // assert_eq!(workspace.zoomed_position, None);
5312 // });
5313
5314 // // Emit closed event on panel 1, which is active
5315 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5316
5317 // // Now the left dock is closed, because panel_1 was the active panel
5318 // workspace.update(cx, |workspace, cx| {
5319 // let right_dock = workspace.right_dock();
5320 // assert!(!right_dock.read(cx).is_open());
5321 // });
5322 // }
5323
5324 pub fn init_test(cx: &mut TestAppContext) {
5325 cx.update(|cx| {
5326 let settings_store = SettingsStore::test(cx);
5327 cx.set_global(settings_store);
5328 theme::init(theme::LoadThemes::JustBase, cx);
5329 language::init(cx);
5330 crate::init_settings(cx);
5331 Project::init_settings(cx);
5332 });
5333 }
5334}