1#![allow(unused_variables, dead_code, unused_mut)]
2// todo!() this is to make transition easier.
3
4pub mod dock;
5pub mod item;
6pub mod notifications;
7pub mod pane;
8pub mod pane_group;
9mod persistence;
10pub mod searchable;
11// todo!()
12mod modal_layer;
13mod shared_screen;
14mod status_bar;
15mod toolbar;
16mod workspace_settings;
17
18use anyhow::{anyhow, Context as _, Result};
19use call::ActiveCall;
20use client::{
21 proto::{self, PeerId},
22 Client, Status, TypedEnvelope, UserStore,
23};
24use collections::{hash_map, HashMap, HashSet};
25use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
26use futures::{
27 channel::{mpsc, oneshot},
28 future::try_join_all,
29 Future, FutureExt, StreamExt,
30};
31use gpui::{
32 actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext,
33 AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
34 FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model,
35 ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled,
36 Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
37 WindowHandle, WindowOptions,
38};
39use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
40use itertools::Itertools;
41use language::{LanguageRegistry, Rope};
42use lazy_static::lazy_static;
43pub use modal_layer::*;
44use node_runtime::NodeRuntime;
45use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
46pub use pane::*;
47pub use pane_group::*;
48pub use persistence::{
49 model::{ItemId, SerializedWorkspace, WorkspaceLocation},
50 WorkspaceDb, DB,
51};
52use postage::stream::Stream;
53use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
54use serde::Deserialize;
55use settings::Settings;
56use shared_screen::SharedScreen;
57use status_bar::StatusBar;
58pub use status_bar::StatusItemView;
59use std::{
60 any::TypeId,
61 borrow::Cow,
62 cmp, env,
63 path::{Path, PathBuf},
64 sync::{atomic::AtomicUsize, Arc},
65 time::Duration,
66};
67use theme::{ActiveTheme, ThemeSettings};
68pub use toolbar::{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.focus_handle(cx).contains_focused(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| {
1593 !panel.focus_handle(cx).contains_focused(cx)
1594 });
1595 }
1596
1597 /// Focus or unfocus the given panel type, depending on the given callback.
1598 fn focus_or_unfocus_panel<T: Panel>(
1599 &mut self,
1600 cx: &mut ViewContext<Self>,
1601 should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1602 ) -> Option<Arc<dyn PanelHandle>> {
1603 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1604 if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1605 let mut focus_center = false;
1606 let mut reveal_dock = false;
1607 let panel = dock.update(cx, |dock, cx| {
1608 dock.activate_panel(panel_index, cx);
1609
1610 let panel = dock.active_panel().cloned();
1611 if let Some(panel) = panel.as_ref() {
1612 if should_focus(&**panel, cx) {
1613 dock.set_open(true, cx);
1614 panel.focus_handle(cx).focus(cx);
1615 reveal_dock = true;
1616 } else {
1617 // if panel.is_zoomed(cx) {
1618 // dock.set_open(false, cx);
1619 // }
1620 focus_center = true;
1621 }
1622 }
1623 panel
1624 });
1625
1626 if focus_center {
1627 self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1628 }
1629
1630 self.serialize_workspace(cx);
1631 cx.notify();
1632 return panel;
1633 }
1634 }
1635 None
1636 }
1637
1638 pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
1639 for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1640 let dock = dock.read(cx);
1641 if let Some(panel) = dock.panel::<T>() {
1642 return Some(panel);
1643 }
1644 }
1645 None
1646 }
1647
1648 fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1649 for pane in &self.panes {
1650 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1651 }
1652
1653 self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1654 self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1655 self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1656 self.zoomed = None;
1657 self.zoomed_position = None;
1658
1659 cx.notify();
1660 }
1661
1662 // #[cfg(any(test, feature = "test-support"))]
1663 // pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1664 // self.zoomed.and_then(|view| view.upgrade(cx))
1665 // }
1666
1667 fn dismiss_zoomed_items_to_reveal(
1668 &mut self,
1669 dock_to_reveal: Option<DockPosition>,
1670 cx: &mut ViewContext<Self>,
1671 ) {
1672 // If a center pane is zoomed, unzoom it.
1673 for pane in &self.panes {
1674 if pane != &self.active_pane || dock_to_reveal.is_some() {
1675 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1676 }
1677 }
1678
1679 // If another dock is zoomed, hide it.
1680 let mut focus_center = false;
1681 for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1682 dock.update(cx, |dock, cx| {
1683 if Some(dock.position()) != dock_to_reveal {
1684 if let Some(panel) = dock.active_panel() {
1685 if panel.is_zoomed(cx) {
1686 focus_center |= panel.focus_handle(cx).contains_focused(cx);
1687 dock.set_open(false, cx);
1688 }
1689 }
1690 }
1691 });
1692 }
1693
1694 if focus_center {
1695 self.active_pane.update(cx, |pane, cx| pane.focus(cx))
1696 }
1697
1698 if self.zoomed_position != dock_to_reveal {
1699 self.zoomed = None;
1700 self.zoomed_position = None;
1701 }
1702
1703 cx.notify();
1704 }
1705
1706 fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
1707 let pane = cx.build_view(|cx| {
1708 Pane::new(
1709 self.weak_handle(),
1710 self.project.clone(),
1711 self.pane_history_timestamp.clone(),
1712 cx,
1713 )
1714 });
1715 cx.subscribe(&pane, Self::handle_pane_event).detach();
1716 self.panes.push(pane.clone());
1717 cx.focus_view(&pane);
1718 cx.emit(Event::PaneAdded(pane.clone()));
1719 pane
1720 }
1721
1722 pub fn add_item_to_center(
1723 &mut self,
1724 item: Box<dyn ItemHandle>,
1725 cx: &mut ViewContext<Self>,
1726 ) -> bool {
1727 if let Some(center_pane) = self.last_active_center_pane.clone() {
1728 if let Some(center_pane) = center_pane.upgrade() {
1729 center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1730 true
1731 } else {
1732 false
1733 }
1734 } else {
1735 false
1736 }
1737 }
1738
1739 pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1740 self.active_pane
1741 .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1742 }
1743
1744 pub fn split_item(
1745 &mut self,
1746 split_direction: SplitDirection,
1747 item: Box<dyn ItemHandle>,
1748 cx: &mut ViewContext<Self>,
1749 ) {
1750 let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
1751 new_pane.update(cx, move |new_pane, cx| {
1752 new_pane.add_item(item, true, true, None, cx)
1753 })
1754 }
1755
1756 pub fn open_abs_path(
1757 &mut self,
1758 abs_path: PathBuf,
1759 visible: bool,
1760 cx: &mut ViewContext<Self>,
1761 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1762 cx.spawn(|workspace, mut cx| async move {
1763 let open_paths_task_result = workspace
1764 .update(&mut cx, |workspace, cx| {
1765 workspace.open_paths(vec![abs_path.clone()], visible, cx)
1766 })
1767 .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1768 .await;
1769 anyhow::ensure!(
1770 open_paths_task_result.len() == 1,
1771 "open abs path {abs_path:?} task returned incorrect number of results"
1772 );
1773 match open_paths_task_result
1774 .into_iter()
1775 .next()
1776 .expect("ensured single task result")
1777 {
1778 Some(open_result) => {
1779 open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1780 }
1781 None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1782 }
1783 })
1784 }
1785
1786 pub fn split_abs_path(
1787 &mut self,
1788 abs_path: PathBuf,
1789 visible: bool,
1790 cx: &mut ViewContext<Self>,
1791 ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1792 let project_path_task =
1793 Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1794 cx.spawn(|this, mut cx| async move {
1795 let (_, path) = project_path_task.await?;
1796 this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1797 .await
1798 })
1799 }
1800
1801 pub fn open_path(
1802 &mut self,
1803 path: impl Into<ProjectPath>,
1804 pane: Option<WeakView<Pane>>,
1805 focus_item: bool,
1806 cx: &mut ViewContext<Self>,
1807 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1808 let pane = pane.unwrap_or_else(|| {
1809 self.last_active_center_pane.clone().unwrap_or_else(|| {
1810 self.panes
1811 .first()
1812 .expect("There must be an active pane")
1813 .downgrade()
1814 })
1815 });
1816
1817 let task = self.load_path(path.into(), cx);
1818 cx.spawn(move |_, mut cx| async move {
1819 let (project_entry_id, build_item) = task.await?;
1820 pane.update(&mut cx, |pane, cx| {
1821 pane.open_item(project_entry_id, focus_item, cx, build_item)
1822 })
1823 })
1824 }
1825
1826 pub fn split_path(
1827 &mut self,
1828 path: impl Into<ProjectPath>,
1829 cx: &mut ViewContext<Self>,
1830 ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1831 let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1832 self.panes
1833 .first()
1834 .expect("There must be an active pane")
1835 .downgrade()
1836 });
1837
1838 if let Member::Pane(center_pane) = &self.center.root {
1839 if center_pane.read(cx).items_len() == 0 {
1840 return self.open_path(path, Some(pane), true, cx);
1841 }
1842 }
1843
1844 let task = self.load_path(path.into(), cx);
1845 cx.spawn(|this, mut cx| async move {
1846 let (project_entry_id, build_item) = task.await?;
1847 this.update(&mut cx, move |this, cx| -> Option<_> {
1848 let pane = pane.upgrade()?;
1849 let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1850 new_pane.update(cx, |new_pane, cx| {
1851 Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1852 })
1853 })
1854 .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1855 })
1856 }
1857
1858 pub(crate) fn load_path(
1859 &mut self,
1860 path: ProjectPath,
1861 cx: &mut ViewContext<Self>,
1862 ) -> Task<
1863 Result<(
1864 ProjectEntryId,
1865 impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1866 )>,
1867 > {
1868 let project = self.project().clone();
1869 let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1870 cx.spawn(|_, mut cx| async move {
1871 let (project_entry_id, project_item) = project_item.await?;
1872 let build_item = cx.update(|_, cx| {
1873 cx.default_global::<ProjectItemBuilders>()
1874 .get(&project_item.entity_type())
1875 .ok_or_else(|| anyhow!("no item builder for project item"))
1876 .cloned()
1877 })??;
1878 let build_item =
1879 move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1880 Ok((project_entry_id, build_item))
1881 })
1882 }
1883
1884 pub fn open_project_item<T>(
1885 &mut self,
1886 project_item: Model<T::Item>,
1887 cx: &mut ViewContext<Self>,
1888 ) -> View<T>
1889 where
1890 T: ProjectItem,
1891 {
1892 use project::Item as _;
1893
1894 let entry_id = project_item.read(cx).entry_id(cx);
1895 if let Some(item) = entry_id
1896 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1897 .and_then(|item| item.downcast())
1898 {
1899 self.activate_item(&item, cx);
1900 return item;
1901 }
1902
1903 let item =
1904 cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1905 self.add_item(Box::new(item.clone()), cx);
1906 item
1907 }
1908
1909 pub fn split_project_item<T>(
1910 &mut self,
1911 project_item: Model<T::Item>,
1912 cx: &mut ViewContext<Self>,
1913 ) -> View<T>
1914 where
1915 T: ProjectItem,
1916 {
1917 use project::Item as _;
1918
1919 let entry_id = project_item.read(cx).entry_id(cx);
1920 if let Some(item) = entry_id
1921 .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1922 .and_then(|item| item.downcast())
1923 {
1924 self.activate_item(&item, cx);
1925 return item;
1926 }
1927
1928 let item =
1929 cx.build_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1930 self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
1931 item
1932 }
1933
1934 pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
1935 if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
1936 self.active_pane.update(cx, |pane, cx| {
1937 pane.add_item(Box::new(shared_screen), false, true, None, cx)
1938 });
1939 }
1940 }
1941
1942 pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
1943 let result = self.panes.iter().find_map(|pane| {
1944 pane.read(cx)
1945 .index_for_item(item)
1946 .map(|ix| (pane.clone(), ix))
1947 });
1948 if let Some((pane, ix)) = result {
1949 pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
1950 true
1951 } else {
1952 false
1953 }
1954 }
1955
1956 fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
1957 let panes = self.center.panes();
1958 if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
1959 cx.focus_view(&pane);
1960 } else {
1961 self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
1962 }
1963 }
1964
1965 pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
1966 let panes = self.center.panes();
1967 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1968 let next_ix = (ix + 1) % panes.len();
1969 let next_pane = panes[next_ix].clone();
1970 cx.focus_view(&next_pane);
1971 }
1972 }
1973
1974 pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
1975 let panes = self.center.panes();
1976 if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
1977 let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
1978 let prev_pane = panes[prev_ix].clone();
1979 cx.focus_view(&prev_pane);
1980 }
1981 }
1982
1983 pub fn activate_pane_in_direction(
1984 &mut self,
1985 direction: SplitDirection,
1986 cx: &mut ViewContext<Self>,
1987 ) {
1988 if let Some(pane) = self.find_pane_in_direction(direction, cx) {
1989 cx.focus_view(pane);
1990 }
1991 }
1992
1993 pub fn swap_pane_in_direction(
1994 &mut self,
1995 direction: SplitDirection,
1996 cx: &mut ViewContext<Self>,
1997 ) {
1998 if let Some(to) = self
1999 .find_pane_in_direction(direction, cx)
2000 .map(|pane| pane.clone())
2001 {
2002 self.center.swap(&self.active_pane.clone(), &to);
2003 cx.notify();
2004 }
2005 }
2006
2007 fn find_pane_in_direction(
2008 &mut self,
2009 direction: SplitDirection,
2010 cx: &mut ViewContext<Self>,
2011 ) -> Option<&View<Pane>> {
2012 let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
2013 return None;
2014 };
2015 let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
2016 let center = match cursor {
2017 Some(cursor) if bounding_box.contains_point(&cursor) => cursor,
2018 _ => bounding_box.center(),
2019 };
2020
2021 let distance_to_next = 1.; //todo(pane dividers styling)
2022
2023 let target = match direction {
2024 SplitDirection::Left => {
2025 Point::new(bounding_box.origin.x - distance_to_next.into(), center.y)
2026 }
2027 SplitDirection::Right => {
2028 Point::new(bounding_box.right() + distance_to_next.into(), center.y)
2029 }
2030 SplitDirection::Up => {
2031 Point::new(center.x, bounding_box.origin.y - distance_to_next.into())
2032 }
2033 SplitDirection::Down => {
2034 Point::new(center.x, bounding_box.top() + distance_to_next.into())
2035 }
2036 };
2037 self.center.pane_at_pixel_position(target)
2038 }
2039
2040 fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2041 if self.active_pane != pane {
2042 self.active_pane = pane.clone();
2043 self.status_bar.update(cx, |status_bar, cx| {
2044 status_bar.set_active_pane(&self.active_pane, cx);
2045 });
2046 self.active_item_path_changed(cx);
2047 self.last_active_center_pane = Some(pane.downgrade());
2048 }
2049
2050 self.dismiss_zoomed_items_to_reveal(None, cx);
2051 if pane.read(cx).is_zoomed() {
2052 self.zoomed = Some(pane.downgrade().into());
2053 } else {
2054 self.zoomed = None;
2055 }
2056 self.zoomed_position = None;
2057 self.update_active_view_for_followers(cx);
2058
2059 cx.notify();
2060 }
2061
2062 fn handle_pane_event(
2063 &mut self,
2064 pane: View<Pane>,
2065 event: &pane::Event,
2066 cx: &mut ViewContext<Self>,
2067 ) {
2068 match event {
2069 pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2070 pane::Event::Split(direction) => {
2071 self.split_and_clone(pane, *direction, cx);
2072 }
2073 pane::Event::Remove => self.remove_pane(pane, cx),
2074 pane::Event::ActivateItem { local } => {
2075 if *local {
2076 self.unfollow(&pane, cx);
2077 }
2078 if &pane == self.active_pane() {
2079 self.active_item_path_changed(cx);
2080 }
2081 }
2082 pane::Event::ChangeItemTitle => {
2083 if pane == self.active_pane {
2084 self.active_item_path_changed(cx);
2085 }
2086 self.update_window_edited(cx);
2087 }
2088 pane::Event::RemoveItem { item_id } => {
2089 self.update_window_edited(cx);
2090 if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2091 if entry.get().entity_id() == pane.entity_id() {
2092 entry.remove();
2093 }
2094 }
2095 }
2096 pane::Event::Focus => {
2097 self.handle_pane_focused(pane.clone(), cx);
2098 }
2099 pane::Event::ZoomIn => {
2100 if pane == self.active_pane {
2101 pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2102 if pane.read(cx).has_focus(cx) {
2103 self.zoomed = Some(pane.downgrade().into());
2104 self.zoomed_position = None;
2105 }
2106 cx.notify();
2107 }
2108 }
2109 pane::Event::ZoomOut => {
2110 pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2111 if self.zoomed_position.is_none() {
2112 self.zoomed = None;
2113 }
2114 cx.notify();
2115 }
2116 }
2117
2118 self.serialize_workspace(cx);
2119 }
2120
2121 pub fn split_pane(
2122 &mut self,
2123 pane_to_split: View<Pane>,
2124 split_direction: SplitDirection,
2125 cx: &mut ViewContext<Self>,
2126 ) -> View<Pane> {
2127 let new_pane = self.add_pane(cx);
2128 self.center
2129 .split(&pane_to_split, &new_pane, split_direction)
2130 .unwrap();
2131 cx.notify();
2132 new_pane
2133 }
2134
2135 pub fn split_and_clone(
2136 &mut self,
2137 pane: View<Pane>,
2138 direction: SplitDirection,
2139 cx: &mut ViewContext<Self>,
2140 ) -> Option<View<Pane>> {
2141 let item = pane.read(cx).active_item()?;
2142 let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2143 let new_pane = self.add_pane(cx);
2144 new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2145 self.center.split(&pane, &new_pane, direction).unwrap();
2146 Some(new_pane)
2147 } else {
2148 None
2149 };
2150 cx.notify();
2151 maybe_pane_handle
2152 }
2153
2154 pub fn split_pane_with_item(
2155 &mut self,
2156 pane_to_split: WeakView<Pane>,
2157 split_direction: SplitDirection,
2158 from: WeakView<Pane>,
2159 item_id_to_move: EntityId,
2160 cx: &mut ViewContext<Self>,
2161 ) {
2162 let Some(pane_to_split) = pane_to_split.upgrade() else {
2163 return;
2164 };
2165 let Some(from) = from.upgrade() else {
2166 return;
2167 };
2168
2169 let new_pane = self.add_pane(cx);
2170 self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2171 self.center
2172 .split(&pane_to_split, &new_pane, split_direction)
2173 .unwrap();
2174 cx.notify();
2175 }
2176
2177 pub fn split_pane_with_project_entry(
2178 &mut self,
2179 pane_to_split: WeakView<Pane>,
2180 split_direction: SplitDirection,
2181 project_entry: ProjectEntryId,
2182 cx: &mut ViewContext<Self>,
2183 ) -> Option<Task<Result<()>>> {
2184 let pane_to_split = pane_to_split.upgrade()?;
2185 let new_pane = self.add_pane(cx);
2186 self.center
2187 .split(&pane_to_split, &new_pane, split_direction)
2188 .unwrap();
2189
2190 let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2191 let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2192 Some(cx.foreground_executor().spawn(async move {
2193 task.await?;
2194 Ok(())
2195 }))
2196 }
2197
2198 pub fn move_item(
2199 &mut self,
2200 source: View<Pane>,
2201 destination: View<Pane>,
2202 item_id_to_move: EntityId,
2203 destination_index: usize,
2204 cx: &mut ViewContext<Self>,
2205 ) {
2206 let item_to_move = source
2207 .read(cx)
2208 .items()
2209 .enumerate()
2210 .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move);
2211
2212 if item_to_move.is_none() {
2213 log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2214 return;
2215 }
2216 let (item_ix, item_handle) = item_to_move.unwrap();
2217 let item_handle = item_handle.clone();
2218
2219 if source != destination {
2220 // Close item from previous pane
2221 source.update(cx, |source, cx| {
2222 source.remove_item(item_ix, false, cx);
2223 });
2224 }
2225
2226 // This automatically removes duplicate items in the pane
2227 destination.update(cx, |destination, cx| {
2228 destination.add_item(item_handle, true, true, Some(destination_index), cx);
2229 destination.focus(cx)
2230 });
2231 }
2232
2233 fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
2234 if self.center.remove(&pane).unwrap() {
2235 self.force_remove_pane(&pane, cx);
2236 self.unfollow(&pane, cx);
2237 self.last_leaders_by_pane.remove(&pane.downgrade());
2238 for removed_item in pane.read(cx).items() {
2239 self.panes_by_item.remove(&removed_item.item_id());
2240 }
2241
2242 cx.notify();
2243 } else {
2244 self.active_item_path_changed(cx);
2245 }
2246 }
2247
2248 pub fn panes(&self) -> &[View<Pane>] {
2249 &self.panes
2250 }
2251
2252 pub fn active_pane(&self) -> &View<Pane> {
2253 &self.active_pane
2254 }
2255
2256 pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
2257 let weak_pane = self.panes_by_item.get(&handle.item_id())?;
2258 weak_pane.upgrade()
2259 }
2260
2261 fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2262 self.follower_states.retain(|_, state| {
2263 if state.leader_id == peer_id {
2264 for item in state.items_by_leader_view_id.values() {
2265 item.set_leader_peer_id(None, cx);
2266 }
2267 false
2268 } else {
2269 true
2270 }
2271 });
2272 cx.notify();
2273 }
2274
2275 fn start_following(
2276 &mut self,
2277 leader_id: PeerId,
2278 cx: &mut ViewContext<Self>,
2279 ) -> Option<Task<Result<()>>> {
2280 let pane = self.active_pane().clone();
2281
2282 self.last_leaders_by_pane
2283 .insert(pane.downgrade(), leader_id);
2284 self.unfollow(&pane, cx);
2285 self.follower_states.insert(
2286 pane.clone(),
2287 FollowerState {
2288 leader_id,
2289 active_view_id: None,
2290 items_by_leader_view_id: Default::default(),
2291 },
2292 );
2293 cx.notify();
2294
2295 let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2296 let project_id = self.project.read(cx).remote_id();
2297 let request = self.app_state.client.request(proto::Follow {
2298 room_id,
2299 project_id,
2300 leader_id: Some(leader_id),
2301 });
2302
2303 Some(cx.spawn(|this, mut cx| async move {
2304 let response = request.await?;
2305 this.update(&mut cx, |this, _| {
2306 let state = this
2307 .follower_states
2308 .get_mut(&pane)
2309 .ok_or_else(|| anyhow!("following interrupted"))?;
2310 state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2311 Some(ViewId::from_proto(active_view_id)?)
2312 } else {
2313 None
2314 };
2315 Ok::<_, anyhow::Error>(())
2316 })??;
2317 Self::add_views_from_leader(
2318 this.clone(),
2319 leader_id,
2320 vec![pane],
2321 response.views,
2322 &mut cx,
2323 )
2324 .await?;
2325 this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2326 Ok(())
2327 }))
2328 }
2329
2330 // pub fn follow_next_collaborator(
2331 // &mut self,
2332 // _: &FollowNextCollaborator,
2333 // cx: &mut ViewContext<Self>,
2334 // ) -> Option<Task<Result<()>>> {
2335 // let collaborators = self.project.read(cx).collaborators();
2336 // let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2337 // let mut collaborators = collaborators.keys().copied();
2338 // for peer_id in collaborators.by_ref() {
2339 // if peer_id == leader_id {
2340 // break;
2341 // }
2342 // }
2343 // collaborators.next()
2344 // } else if let Some(last_leader_id) =
2345 // self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2346 // {
2347 // if collaborators.contains_key(last_leader_id) {
2348 // Some(*last_leader_id)
2349 // } else {
2350 // None
2351 // }
2352 // } else {
2353 // None
2354 // };
2355
2356 // let pane = self.active_pane.clone();
2357 // let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
2358 // else {
2359 // return None;
2360 // };
2361 // if Some(leader_id) == self.unfollow(&pane, cx) {
2362 // return None;
2363 // }
2364 // self.follow(leader_id, cx)
2365 // }
2366
2367 pub fn follow(
2368 &mut self,
2369 leader_id: PeerId,
2370 cx: &mut ViewContext<Self>,
2371 ) -> Option<Task<Result<()>>> {
2372 let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
2373 let project = self.project.read(cx);
2374
2375 let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
2376 return None;
2377 };
2378
2379 let other_project_id = match remote_participant.location {
2380 call::ParticipantLocation::External => None,
2381 call::ParticipantLocation::UnsharedProject => None,
2382 call::ParticipantLocation::SharedProject { project_id } => {
2383 if Some(project_id) == project.remote_id() {
2384 None
2385 } else {
2386 Some(project_id)
2387 }
2388 }
2389 };
2390
2391 // if they are active in another project, follow there.
2392 if let Some(project_id) = other_project_id {
2393 let app_state = self.app_state.clone();
2394 return Some(crate::join_remote_project(
2395 project_id,
2396 remote_participant.user.id,
2397 app_state,
2398 cx,
2399 ));
2400 }
2401
2402 // if you're already following, find the right pane and focus it.
2403 for (pane, state) in &self.follower_states {
2404 if leader_id == state.leader_id {
2405 cx.focus_view(pane);
2406 return None;
2407 }
2408 }
2409
2410 // Otherwise, follow.
2411 self.start_following(leader_id, cx)
2412 }
2413
2414 pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
2415 let state = self.follower_states.remove(pane)?;
2416 let leader_id = state.leader_id;
2417 for (_, item) in state.items_by_leader_view_id {
2418 item.set_leader_peer_id(None, cx);
2419 }
2420
2421 if self
2422 .follower_states
2423 .values()
2424 .all(|state| state.leader_id != state.leader_id)
2425 {
2426 let project_id = self.project.read(cx).remote_id();
2427 let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
2428 self.app_state
2429 .client
2430 .send(proto::Unfollow {
2431 room_id,
2432 project_id,
2433 leader_id: Some(leader_id),
2434 })
2435 .log_err();
2436 }
2437
2438 cx.notify();
2439 Some(leader_id)
2440 }
2441
2442 // pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2443 // self.follower_states
2444 // .values()
2445 // .any(|state| state.leader_id == peer_id)
2446 // }
2447
2448 fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2449 let active_entry = self.active_project_path(cx);
2450 self.project
2451 .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2452 self.update_window_title(cx);
2453 }
2454
2455 fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2456 let project = self.project().read(cx);
2457 let mut title = String::new();
2458
2459 if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2460 let filename = path
2461 .path
2462 .file_name()
2463 .map(|s| s.to_string_lossy())
2464 .or_else(|| {
2465 Some(Cow::Borrowed(
2466 project
2467 .worktree_for_id(path.worktree_id, cx)?
2468 .read(cx)
2469 .root_name(),
2470 ))
2471 });
2472
2473 if let Some(filename) = filename {
2474 title.push_str(filename.as_ref());
2475 title.push_str(" β ");
2476 }
2477 }
2478
2479 for (i, name) in project.worktree_root_names(cx).enumerate() {
2480 if i > 0 {
2481 title.push_str(", ");
2482 }
2483 title.push_str(name);
2484 }
2485
2486 if title.is_empty() {
2487 title = "empty project".to_string();
2488 }
2489
2490 if project.is_remote() {
2491 title.push_str(" β");
2492 } else if project.is_shared() {
2493 title.push_str(" β");
2494 }
2495
2496 cx.set_window_title(&title);
2497 }
2498
2499 fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2500 let is_edited = !self.project.read(cx).is_read_only()
2501 && self
2502 .items(cx)
2503 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2504 if is_edited != self.window_edited {
2505 self.window_edited = is_edited;
2506 // todo!()
2507 // cx.set_window_edited(self.window_edited)
2508 }
2509 }
2510
2511 // fn render_disconnected_overlay(
2512 // &self,
2513 // cx: &mut ViewContext<Workspace>,
2514 // ) -> Option<AnyElement<Workspace>> {
2515 // if self.project.read(cx).is_read_only() {
2516 // enum DisconnectedOverlay {}
2517 // Some(
2518 // MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
2519 // let theme = &theme::current(cx);
2520 // Label::new(
2521 // "Your connection to the remote project has been lost.",
2522 // theme.workspace.disconnected_overlay.text.clone(),
2523 // )
2524 // .aligned()
2525 // .contained()
2526 // .with_style(theme.workspace.disconnected_overlay.container)
2527 // })
2528 // .with_cursor_style(CursorStyle::Arrow)
2529 // .capture_all()
2530 // .into_any_named("disconnected overlay"),
2531 // )
2532 // } else {
2533 // None
2534 // }
2535 // }
2536
2537 fn render_notifications(&self, cx: &ViewContext<Self>) -> Option<Div> {
2538 if self.notifications.is_empty() {
2539 None
2540 } else {
2541 Some(
2542 div()
2543 .absolute()
2544 .z_index(100)
2545 .right_3()
2546 .bottom_3()
2547 .w_96()
2548 .h_full()
2549 .flex()
2550 .flex_col()
2551 .justify_end()
2552 .gap_2()
2553 .children(
2554 self.notifications
2555 .iter()
2556 .map(|(_, _, notification)| notification.to_any()),
2557 ),
2558 )
2559 }
2560 }
2561
2562 // RPC handlers
2563
2564 fn handle_follow(
2565 &mut self,
2566 follower_project_id: Option<u64>,
2567 cx: &mut ViewContext<Self>,
2568 ) -> proto::FollowResponse {
2569 let client = &self.app_state.client;
2570 let project_id = self.project.read(cx).remote_id();
2571
2572 let active_view_id = self.active_item(cx).and_then(|i| {
2573 Some(
2574 i.to_followable_item_handle(cx)?
2575 .remote_id(client, cx)?
2576 .to_proto(),
2577 )
2578 });
2579
2580 cx.notify();
2581
2582 self.last_active_view_id = active_view_id.clone();
2583 proto::FollowResponse {
2584 active_view_id,
2585 views: self
2586 .panes()
2587 .iter()
2588 .flat_map(|pane| {
2589 let leader_id = self.leader_for_pane(pane);
2590 pane.read(cx).items().filter_map({
2591 let cx = &cx;
2592 move |item| {
2593 let item = item.to_followable_item_handle(cx)?;
2594 if (project_id.is_none() || project_id != follower_project_id)
2595 && item.is_project_item(cx)
2596 {
2597 return None;
2598 }
2599 let id = item.remote_id(client, cx)?.to_proto();
2600 let variant = item.to_state_proto(cx)?;
2601 Some(proto::View {
2602 id: Some(id),
2603 leader_id,
2604 variant: Some(variant),
2605 })
2606 }
2607 })
2608 })
2609 .collect(),
2610 }
2611 }
2612
2613 fn handle_update_followers(
2614 &mut self,
2615 leader_id: PeerId,
2616 message: proto::UpdateFollowers,
2617 _cx: &mut ViewContext<Self>,
2618 ) {
2619 self.leader_updates_tx
2620 .unbounded_send((leader_id, message))
2621 .ok();
2622 }
2623
2624 async fn process_leader_update(
2625 this: &WeakView<Self>,
2626 leader_id: PeerId,
2627 update: proto::UpdateFollowers,
2628 cx: &mut AsyncWindowContext,
2629 ) -> Result<()> {
2630 dbg!("process_leader_update", &update);
2631
2632 match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2633 proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2634 this.update(cx, |this, _| {
2635 for (_, state) in &mut this.follower_states {
2636 if state.leader_id == leader_id {
2637 state.active_view_id =
2638 if let Some(active_view_id) = update_active_view.id.clone() {
2639 Some(ViewId::from_proto(active_view_id)?)
2640 } else {
2641 None
2642 };
2643 }
2644 }
2645 anyhow::Ok(())
2646 })??;
2647 }
2648 proto::update_followers::Variant::UpdateView(update_view) => {
2649 let variant = update_view
2650 .variant
2651 .ok_or_else(|| anyhow!("missing update view variant"))?;
2652 let id = update_view
2653 .id
2654 .ok_or_else(|| anyhow!("missing update view id"))?;
2655 let mut tasks = Vec::new();
2656 this.update(cx, |this, cx| {
2657 let project = this.project.clone();
2658 for (_, state) in &mut this.follower_states {
2659 if state.leader_id == leader_id {
2660 let view_id = ViewId::from_proto(id.clone())?;
2661 if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2662 tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2663 }
2664 }
2665 }
2666 anyhow::Ok(())
2667 })??;
2668 try_join_all(tasks).await.log_err();
2669 }
2670 proto::update_followers::Variant::CreateView(view) => {
2671 let panes = this.update(cx, |this, _| {
2672 this.follower_states
2673 .iter()
2674 .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
2675 .cloned()
2676 .collect()
2677 })?;
2678 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2679 }
2680 }
2681 this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2682 Ok(())
2683 }
2684
2685 async fn add_views_from_leader(
2686 this: WeakView<Self>,
2687 leader_id: PeerId,
2688 panes: Vec<View<Pane>>,
2689 views: Vec<proto::View>,
2690 cx: &mut AsyncWindowContext,
2691 ) -> Result<()> {
2692 let this = this.upgrade().context("workspace dropped")?;
2693
2694 let item_builders = cx.update(|_, cx| {
2695 cx.default_global::<FollowableItemBuilders>()
2696 .values()
2697 .map(|b| b.0)
2698 .collect::<Vec<_>>()
2699 })?;
2700
2701 let mut item_tasks_by_pane = HashMap::default();
2702 for pane in panes {
2703 let mut item_tasks = Vec::new();
2704 let mut leader_view_ids = Vec::new();
2705 for view in &views {
2706 let Some(id) = &view.id else { continue };
2707 let id = ViewId::from_proto(id.clone())?;
2708 let mut variant = view.variant.clone();
2709 if variant.is_none() {
2710 Err(anyhow!("missing view variant"))?;
2711 }
2712 for build_item in &item_builders {
2713 let task = cx.update(|_, cx| {
2714 build_item(pane.clone(), this.clone(), id, &mut variant, cx)
2715 })?;
2716 if let Some(task) = task {
2717 item_tasks.push(task);
2718 leader_view_ids.push(id);
2719 break;
2720 } else {
2721 assert!(variant.is_some());
2722 }
2723 }
2724 }
2725
2726 item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2727 }
2728
2729 for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2730 let items = futures::future::try_join_all(item_tasks).await?;
2731 this.update(cx, |this, cx| {
2732 let state = this.follower_states.get_mut(&pane)?;
2733 for (id, item) in leader_view_ids.into_iter().zip(items) {
2734 item.set_leader_peer_id(Some(leader_id), cx);
2735 state.items_by_leader_view_id.insert(id, item);
2736 }
2737
2738 Some(())
2739 })?;
2740 }
2741 Ok(())
2742 }
2743
2744 fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
2745 let mut is_project_item = true;
2746 let mut update = proto::UpdateActiveView::default();
2747 if self.active_pane.read(cx).has_focus(cx) {
2748 let item = self
2749 .active_item(cx)
2750 .and_then(|item| item.to_followable_item_handle(cx));
2751 if let Some(item) = item {
2752 is_project_item = item.is_project_item(cx);
2753 update = proto::UpdateActiveView {
2754 id: item
2755 .remote_id(&self.app_state.client, cx)
2756 .map(|id| id.to_proto()),
2757 leader_id: self.leader_for_pane(&self.active_pane),
2758 };
2759 }
2760 }
2761
2762 if update.id != self.last_active_view_id {
2763 self.last_active_view_id = update.id.clone();
2764 self.update_followers(
2765 is_project_item,
2766 proto::update_followers::Variant::UpdateActiveView(update),
2767 cx,
2768 );
2769 }
2770 }
2771
2772 fn update_followers(
2773 &self,
2774 project_only: bool,
2775 update: proto::update_followers::Variant,
2776 cx: &mut WindowContext,
2777 ) -> Option<()> {
2778 let project_id = if project_only {
2779 self.project.read(cx).remote_id()
2780 } else {
2781 None
2782 };
2783 self.app_state().workspace_store.update(cx, |store, cx| {
2784 store.update_followers(project_id, update, cx)
2785 })
2786 }
2787
2788 pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
2789 self.follower_states.get(pane).map(|state| state.leader_id)
2790 }
2791
2792 fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2793 cx.notify();
2794
2795 let call = self.active_call()?;
2796 let room = call.read(cx).room()?.read(cx);
2797 let participant = room.remote_participant_for_peer_id(leader_id)?;
2798 let mut items_to_activate = Vec::new();
2799
2800 let leader_in_this_app;
2801 let leader_in_this_project;
2802 match participant.location {
2803 call::ParticipantLocation::SharedProject { project_id } => {
2804 leader_in_this_app = true;
2805 leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
2806 }
2807 call::ParticipantLocation::UnsharedProject => {
2808 leader_in_this_app = true;
2809 leader_in_this_project = false;
2810 }
2811 call::ParticipantLocation::External => {
2812 leader_in_this_app = false;
2813 leader_in_this_project = false;
2814 }
2815 };
2816
2817 for (pane, state) in &self.follower_states {
2818 if state.leader_id != leader_id {
2819 continue;
2820 }
2821 if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
2822 if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
2823 if leader_in_this_project || !item.is_project_item(cx) {
2824 items_to_activate.push((pane.clone(), item.boxed_clone()));
2825 }
2826 } else {
2827 log::warn!(
2828 "unknown view id {:?} for leader {:?}",
2829 active_view_id,
2830 leader_id
2831 );
2832 }
2833 continue;
2834 }
2835
2836 if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2837 items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2838 }
2839 }
2840
2841 for (pane, item) in items_to_activate {
2842 let pane_was_focused = pane.read(cx).has_focus(cx);
2843 if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2844 pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2845 } else {
2846 pane.update(cx, |pane, cx| {
2847 pane.add_item(item.boxed_clone(), false, false, None, cx)
2848 });
2849 }
2850
2851 if pane_was_focused {
2852 pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2853 }
2854 }
2855
2856 None
2857 }
2858
2859 fn shared_screen_for_peer(
2860 &self,
2861 peer_id: PeerId,
2862 pane: &View<Pane>,
2863 cx: &mut ViewContext<Self>,
2864 ) -> Option<View<SharedScreen>> {
2865 let call = self.active_call()?;
2866 let room = call.read(cx).room()?.read(cx);
2867 let participant = room.remote_participant_for_peer_id(peer_id)?;
2868 let track = participant.video_tracks.values().next()?.clone();
2869 let user = participant.user.clone();
2870
2871 for item in pane.read(cx).items_of_type::<SharedScreen>() {
2872 if item.read(cx).peer_id == peer_id {
2873 return Some(item);
2874 }
2875 }
2876
2877 Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2878 }
2879
2880 pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
2881 if cx.is_window_active() {
2882 self.update_active_view_for_followers(cx);
2883 cx.background_executor()
2884 .spawn(persistence::DB.update_timestamp(self.database_id()))
2885 .detach();
2886 } else {
2887 for pane in &self.panes {
2888 pane.update(cx, |pane, cx| {
2889 if let Some(item) = pane.active_item() {
2890 item.workspace_deactivated(cx);
2891 }
2892 if matches!(
2893 WorkspaceSettings::get_global(cx).autosave,
2894 AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2895 ) {
2896 for item in pane.items() {
2897 Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2898 .detach_and_log_err(cx);
2899 }
2900 }
2901 });
2902 }
2903 }
2904 }
2905
2906 fn active_call(&self) -> Option<&Model<ActiveCall>> {
2907 self.active_call.as_ref().map(|(call, _)| call)
2908 }
2909
2910 fn on_active_call_event(
2911 &mut self,
2912 _: Model<ActiveCall>,
2913 event: &call::room::Event,
2914 cx: &mut ViewContext<Self>,
2915 ) {
2916 match event {
2917 call::room::Event::ParticipantLocationChanged { participant_id }
2918 | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2919 self.leader_updated(*participant_id, cx);
2920 }
2921 _ => {}
2922 }
2923 }
2924
2925 pub fn database_id(&self) -> WorkspaceId {
2926 self.database_id
2927 }
2928
2929 fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2930 let project = self.project().read(cx);
2931
2932 if project.is_local() {
2933 Some(
2934 project
2935 .visible_worktrees(cx)
2936 .map(|worktree| worktree.read(cx).abs_path())
2937 .collect::<Vec<_>>()
2938 .into(),
2939 )
2940 } else {
2941 None
2942 }
2943 }
2944
2945 fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2946 match member {
2947 Member::Axis(PaneAxis { members, .. }) => {
2948 for child in members.iter() {
2949 self.remove_panes(child.clone(), cx)
2950 }
2951 }
2952 Member::Pane(pane) => {
2953 self.force_remove_pane(&pane, cx);
2954 }
2955 }
2956 }
2957
2958 fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
2959 self.panes.retain(|p| p != pane);
2960 self.panes
2961 .last()
2962 .unwrap()
2963 .update(cx, |pane, cx| pane.focus(cx));
2964 if self.last_active_center_pane == Some(pane.downgrade()) {
2965 self.last_active_center_pane = None;
2966 }
2967 cx.notify();
2968 }
2969
2970 fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2971 self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
2972 cx.background_executor()
2973 .timer(Duration::from_millis(100))
2974 .await;
2975 this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
2976 .log_err();
2977 }));
2978 }
2979
2980 fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
2981 fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
2982 let (items, active) = {
2983 let pane = pane_handle.read(cx);
2984 let active_item_id = pane.active_item().map(|item| item.item_id());
2985 (
2986 pane.items()
2987 .filter_map(|item_handle| {
2988 Some(SerializedItem {
2989 kind: Arc::from(item_handle.serialized_item_kind()?),
2990 item_id: item_handle.item_id().as_u64(),
2991 active: Some(item_handle.item_id()) == active_item_id,
2992 })
2993 })
2994 .collect::<Vec<_>>(),
2995 pane.has_focus(cx),
2996 )
2997 };
2998
2999 SerializedPane::new(items, active)
3000 }
3001
3002 fn build_serialized_pane_group(
3003 pane_group: &Member,
3004 cx: &WindowContext,
3005 ) -> SerializedPaneGroup {
3006 match pane_group {
3007 Member::Axis(PaneAxis {
3008 axis,
3009 members,
3010 flexes,
3011 bounding_boxes: _,
3012 }) => SerializedPaneGroup::Group {
3013 axis: *axis,
3014 children: members
3015 .iter()
3016 .map(|member| build_serialized_pane_group(member, cx))
3017 .collect::<Vec<_>>(),
3018 flexes: Some(flexes.lock().clone()),
3019 },
3020 Member::Pane(pane_handle) => {
3021 SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3022 }
3023 }
3024 }
3025
3026 fn build_serialized_docks(
3027 this: &Workspace,
3028 cx: &mut ViewContext<Workspace>,
3029 ) -> DockStructure {
3030 let left_dock = this.left_dock.read(cx);
3031 let left_visible = left_dock.is_open();
3032 let left_active_panel = left_dock
3033 .visible_panel()
3034 .and_then(|panel| Some(panel.persistent_name().to_string()));
3035 let left_dock_zoom = left_dock
3036 .visible_panel()
3037 .map(|panel| panel.is_zoomed(cx))
3038 .unwrap_or(false);
3039
3040 let right_dock = this.right_dock.read(cx);
3041 let right_visible = right_dock.is_open();
3042 let right_active_panel = right_dock
3043 .visible_panel()
3044 .and_then(|panel| Some(panel.persistent_name().to_string()));
3045 let right_dock_zoom = right_dock
3046 .visible_panel()
3047 .map(|panel| panel.is_zoomed(cx))
3048 .unwrap_or(false);
3049
3050 let bottom_dock = this.bottom_dock.read(cx);
3051 let bottom_visible = bottom_dock.is_open();
3052 let bottom_active_panel = bottom_dock
3053 .visible_panel()
3054 .and_then(|panel| Some(panel.persistent_name().to_string()));
3055 let bottom_dock_zoom = bottom_dock
3056 .visible_panel()
3057 .map(|panel| panel.is_zoomed(cx))
3058 .unwrap_or(false);
3059
3060 DockStructure {
3061 left: DockData {
3062 visible: left_visible,
3063 active_panel: left_active_panel,
3064 zoom: left_dock_zoom,
3065 },
3066 right: DockData {
3067 visible: right_visible,
3068 active_panel: right_active_panel,
3069 zoom: right_dock_zoom,
3070 },
3071 bottom: DockData {
3072 visible: bottom_visible,
3073 active_panel: bottom_active_panel,
3074 zoom: bottom_dock_zoom,
3075 },
3076 }
3077 }
3078
3079 if let Some(location) = self.location(cx) {
3080 // Load bearing special case:
3081 // - with_local_workspace() relies on this to not have other stuff open
3082 // when you open your log
3083 if !location.paths().is_empty() {
3084 let center_group = build_serialized_pane_group(&self.center.root, cx);
3085 let docks = build_serialized_docks(self, cx);
3086
3087 let serialized_workspace = SerializedWorkspace {
3088 id: self.database_id,
3089 location,
3090 center_group,
3091 bounds: Default::default(),
3092 display: Default::default(),
3093 docks,
3094 };
3095
3096 cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
3097 .detach();
3098 }
3099 }
3100 }
3101
3102 pub(crate) fn load_workspace(
3103 serialized_workspace: SerializedWorkspace,
3104 paths_to_open: Vec<Option<ProjectPath>>,
3105 cx: &mut ViewContext<Workspace>,
3106 ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
3107 cx.spawn(|workspace, mut cx| async move {
3108 let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| {
3109 (
3110 workspace.project().clone(),
3111 workspace.last_active_center_pane.clone(),
3112 )
3113 })?;
3114
3115 let mut center_group = None;
3116 let mut center_items = None;
3117
3118 // Traverse the splits tree and add to things
3119 if let Some((group, active_pane, items)) = serialized_workspace
3120 .center_group
3121 .deserialize(
3122 &project,
3123 serialized_workspace.id,
3124 workspace.clone(),
3125 &mut cx,
3126 )
3127 .await
3128 {
3129 center_items = Some(items);
3130 center_group = Some((group, active_pane))
3131 }
3132
3133 let mut items_by_project_path = cx.update(|_, cx| {
3134 center_items
3135 .unwrap_or_default()
3136 .into_iter()
3137 .filter_map(|item| {
3138 let item = item?;
3139 let project_path = item.project_path(cx)?;
3140 Some((project_path, item))
3141 })
3142 .collect::<HashMap<_, _>>()
3143 })?;
3144
3145 let opened_items = paths_to_open
3146 .into_iter()
3147 .map(|path_to_open| {
3148 path_to_open
3149 .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
3150 })
3151 .collect::<Vec<_>>();
3152
3153 // Remove old panes from workspace panes list
3154 workspace.update(&mut cx, |workspace, cx| {
3155 if let Some((center_group, active_pane)) = center_group {
3156 workspace.remove_panes(workspace.center.root.clone(), cx);
3157
3158 // Swap workspace center group
3159 workspace.center = PaneGroup::with_root(center_group);
3160 workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
3161 if let Some(active_pane) = active_pane {
3162 workspace.active_pane = active_pane;
3163 cx.focus_self();
3164 } else {
3165 workspace.active_pane = workspace.center.first_pane().clone();
3166 }
3167 }
3168
3169 let docks = serialized_workspace.docks;
3170 workspace.left_dock.update(cx, |dock, cx| {
3171 dock.set_open(docks.left.visible, cx);
3172 if let Some(active_panel) = docks.left.active_panel {
3173 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3174 dock.activate_panel(ix, cx);
3175 }
3176 }
3177 dock.active_panel()
3178 .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
3179 if docks.left.visible && docks.left.zoom {
3180 cx.focus_self()
3181 }
3182 });
3183 // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3184 workspace.right_dock.update(cx, |dock, cx| {
3185 dock.set_open(docks.right.visible, cx);
3186 if let Some(active_panel) = docks.right.active_panel {
3187 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3188 dock.activate_panel(ix, cx);
3189 }
3190 }
3191 dock.active_panel()
3192 .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
3193
3194 if docks.right.visible && docks.right.zoom {
3195 cx.focus_self()
3196 }
3197 });
3198 workspace.bottom_dock.update(cx, |dock, cx| {
3199 dock.set_open(docks.bottom.visible, cx);
3200 if let Some(active_panel) = docks.bottom.active_panel {
3201 if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
3202 dock.activate_panel(ix, cx);
3203 }
3204 }
3205
3206 dock.active_panel()
3207 .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
3208
3209 if docks.bottom.visible && docks.bottom.zoom {
3210 cx.focus_self()
3211 }
3212 });
3213
3214 cx.notify();
3215 })?;
3216
3217 // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3218 workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3219
3220 Ok(opened_items)
3221 })
3222 }
3223
3224 fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
3225 self.add_workspace_actions_listeners(div, cx)
3226 // cx.add_async_action(Workspace::open);
3227 // cx.add_async_action(Workspace::follow_next_collaborator);
3228 // cx.add_async_action(Workspace::close);
3229 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3230 .on_action(cx.listener(Self::close_all_items_and_panes))
3231 // cx.add_global_action(Workspace::close_global);
3232 // cx.add_global_action(restart);
3233 .on_action(cx.listener(Self::save_all))
3234 .on_action(cx.listener(Self::add_folder_to_project))
3235 .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3236 let pane = workspace.active_pane().clone();
3237 workspace.unfollow(&pane, cx);
3238 }))
3239 .on_action(cx.listener(|workspace, action: &Save, cx| {
3240 workspace
3241 .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
3242 .detach_and_log_err(cx);
3243 }))
3244 .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
3245 workspace
3246 .save_active_item(SaveIntent::SaveAs, cx)
3247 .detach_and_log_err(cx);
3248 }))
3249 .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3250 workspace.activate_previous_pane(cx)
3251 }))
3252 .on_action(
3253 cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3254 )
3255 .on_action(
3256 cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3257 workspace.activate_pane_in_direction(action.0, cx)
3258 }),
3259 )
3260 .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3261 workspace.swap_pane_in_direction(action.0, cx)
3262 }))
3263 .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3264 this.toggle_dock(DockPosition::Left, cx);
3265 }))
3266 .on_action(
3267 cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3268 workspace.toggle_dock(DockPosition::Right, cx);
3269 }),
3270 )
3271 .on_action(
3272 cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3273 workspace.toggle_dock(DockPosition::Bottom, cx);
3274 }),
3275 )
3276 .on_action(
3277 cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3278 workspace.close_all_docks(cx);
3279 }),
3280 )
3281 // cx.add_action(Workspace::activate_pane_at_index);
3282 // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3283 // workspace.reopen_closed_item(cx).detach();
3284 // });
3285 // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3286 // workspace
3287 // .go_back(workspace.active_pane().downgrade(), cx)
3288 // .detach();
3289 // });
3290 // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3291 // workspace
3292 // .go_forward(workspace.active_pane().downgrade(), cx)
3293 // .detach();
3294 // });
3295
3296 // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3297 // cx.spawn(|workspace, mut cx| async move {
3298 // let err = install_cli::install_cli(&cx)
3299 // .await
3300 // .context("Failed to create CLI symlink");
3301
3302 // workspace.update(&mut cx, |workspace, cx| {
3303 // if matches!(err, Err(_)) {
3304 // err.notify_err(workspace, cx);
3305 // } else {
3306 // workspace.show_notification(1, cx, |cx| {
3307 // cx.build_view(|_| {
3308 // MessageNotification::new("Successfully installed the `zed` binary")
3309 // })
3310 // });
3311 // }
3312 // })
3313 // })
3314 // .detach();
3315 // });
3316 }
3317
3318 #[cfg(any(test, feature = "test-support"))]
3319 pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3320 use node_runtime::FakeNodeRuntime;
3321
3322 let client = project.read(cx).client();
3323 let user_store = project.read(cx).user_store();
3324
3325 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3326 let app_state = Arc::new(AppState {
3327 languages: project.read(cx).languages().clone(),
3328 workspace_store,
3329 client,
3330 user_store,
3331 fs: project.read(cx).fs().clone(),
3332 build_window_options: |_, _, _| Default::default(),
3333 node_runtime: FakeNodeRuntime::new(),
3334 });
3335 let workspace = Self::new(0, project, app_state, cx);
3336 workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3337 workspace
3338 }
3339
3340 // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3341 // let dock = match position {
3342 // DockPosition::Left => &self.left_dock,
3343 // DockPosition::Right => &self.right_dock,
3344 // DockPosition::Bottom => &self.bottom_dock,
3345 // };
3346 // let active_panel = dock.read(cx).visible_panel()?;
3347 // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3348 // dock.read(cx).render_placeholder(cx)
3349 // } else {
3350 // ChildView::new(dock, cx).into_any()
3351 // };
3352
3353 // Some(
3354 // element
3355 // .constrained()
3356 // .dynamically(move |constraint, _, cx| match position {
3357 // DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3358 // Vector2F::new(20., constraint.min.y()),
3359 // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3360 // ),
3361 // DockPosition::Bottom => SizeConstraint::new(
3362 // Vector2F::new(constraint.min.x(), 20.),
3363 // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3364 // ),
3365 // })
3366 // .into_any(),
3367 // )
3368 // }
3369 // }
3370 pub fn register_action<A: Action>(
3371 &mut self,
3372 callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3373 ) -> &mut Self {
3374 let callback = Arc::new(callback);
3375
3376 self.workspace_actions.push(Box::new(move |div, cx| {
3377 let callback = callback.clone();
3378 div.on_action(
3379 cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3380 )
3381 }));
3382 self
3383 }
3384
3385 fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3386 let mut div = div
3387 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3388 .on_action(cx.listener(Self::close_all_items_and_panes))
3389 .on_action(cx.listener(Self::add_folder_to_project))
3390 .on_action(cx.listener(Self::save_all))
3391 .on_action(cx.listener(Self::open));
3392 for action in self.workspace_actions.iter() {
3393 div = (action)(div, cx)
3394 }
3395 div
3396 }
3397
3398 pub fn active_modal<V: ManagedView + 'static>(
3399 &mut self,
3400 cx: &ViewContext<Self>,
3401 ) -> Option<View<V>> {
3402 self.modal_layer.read(cx).active_modal()
3403 }
3404
3405 pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3406 where
3407 B: FnOnce(&mut ViewContext<V>) -> V,
3408 {
3409 self.modal_layer
3410 .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3411 }
3412}
3413
3414fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3415 let display_origin = cx
3416 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3417 .ok()??;
3418 ZED_WINDOW_POSITION
3419 .zip(*ZED_WINDOW_SIZE)
3420 .map(|(position, size)| {
3421 WindowBounds::Fixed(Bounds {
3422 origin: display_origin + position,
3423 size,
3424 })
3425 })
3426}
3427
3428fn open_items(
3429 serialized_workspace: Option<SerializedWorkspace>,
3430 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3431 app_state: Arc<AppState>,
3432 cx: &mut ViewContext<Workspace>,
3433) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3434 let restored_items = serialized_workspace.map(|serialized_workspace| {
3435 Workspace::load_workspace(
3436 serialized_workspace,
3437 project_paths_to_open
3438 .iter()
3439 .map(|(_, project_path)| project_path)
3440 .cloned()
3441 .collect(),
3442 cx,
3443 )
3444 });
3445
3446 cx.spawn(|workspace, mut cx| async move {
3447 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3448
3449 if let Some(restored_items) = restored_items {
3450 let restored_items = restored_items.await?;
3451
3452 let restored_project_paths = restored_items
3453 .iter()
3454 .filter_map(|item| {
3455 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3456 .ok()
3457 .flatten()
3458 })
3459 .collect::<HashSet<_>>();
3460
3461 for restored_item in restored_items {
3462 opened_items.push(restored_item.map(Ok));
3463 }
3464
3465 project_paths_to_open
3466 .iter_mut()
3467 .for_each(|(_, project_path)| {
3468 if let Some(project_path_to_open) = project_path {
3469 if restored_project_paths.contains(project_path_to_open) {
3470 *project_path = None;
3471 }
3472 }
3473 });
3474 } else {
3475 for _ in 0..project_paths_to_open.len() {
3476 opened_items.push(None);
3477 }
3478 }
3479 assert!(opened_items.len() == project_paths_to_open.len());
3480
3481 let tasks =
3482 project_paths_to_open
3483 .into_iter()
3484 .enumerate()
3485 .map(|(i, (abs_path, project_path))| {
3486 let workspace = workspace.clone();
3487 cx.spawn(|mut cx| {
3488 let fs = app_state.fs.clone();
3489 async move {
3490 let file_project_path = project_path?;
3491 if fs.is_file(&abs_path).await {
3492 Some((
3493 i,
3494 workspace
3495 .update(&mut cx, |workspace, cx| {
3496 workspace.open_path(file_project_path, None, true, cx)
3497 })
3498 .log_err()?
3499 .await,
3500 ))
3501 } else {
3502 None
3503 }
3504 }
3505 })
3506 });
3507
3508 let tasks = tasks.collect::<Vec<_>>();
3509
3510 let tasks = futures::future::join_all(tasks.into_iter());
3511 for maybe_opened_path in tasks.await.into_iter() {
3512 if let Some((i, path_open_result)) = maybe_opened_path {
3513 opened_items[i] = Some(path_open_result);
3514 }
3515 }
3516
3517 Ok(opened_items)
3518 })
3519}
3520
3521fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3522 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3523
3524 workspace
3525 .update(cx, |workspace, cx| {
3526 if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3527 workspace.show_notification_once(0, cx, |cx| {
3528 cx.build_view(|_| {
3529 MessageNotification::new("Failed to load the database file.")
3530 .with_click_message("Click to let us know about this error")
3531 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3532 })
3533 });
3534 }
3535 })
3536 .log_err();
3537}
3538
3539impl FocusableView for Workspace {
3540 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3541 self.active_pane.focus_handle(cx)
3542 }
3543}
3544
3545impl Render for Workspace {
3546 type Element = Div;
3547
3548 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3549 let mut context = KeyContext::default();
3550 context.add("Workspace");
3551
3552 let (ui_font, ui_font_size) = {
3553 let theme_settings = ThemeSettings::get_global(cx);
3554 (
3555 theme_settings.ui_font.family.clone(),
3556 theme_settings.ui_font_size.clone(),
3557 )
3558 };
3559
3560 cx.set_rem_size(ui_font_size);
3561
3562 self.actions(div(), cx)
3563 .key_context(context)
3564 .relative()
3565 .size_full()
3566 .flex()
3567 .flex_col()
3568 .font(ui_font)
3569 .gap_0()
3570 .justify_start()
3571 .items_start()
3572 .text_color(cx.theme().colors().text)
3573 .bg(cx.theme().colors().background)
3574 .border()
3575 .border_color(cx.theme().colors().border)
3576 .children(self.titlebar_item.clone())
3577 .child(
3578 div()
3579 .id("workspace")
3580 .relative()
3581 .flex_1()
3582 .w_full()
3583 .flex()
3584 .overflow_hidden()
3585 .border_t()
3586 .border_b()
3587 .border_color(cx.theme().colors().border)
3588 .child(self.modal_layer.clone())
3589 .child(
3590 div()
3591 .flex()
3592 .flex_row()
3593 .flex_1()
3594 .h_full()
3595 // Left Dock
3596 .child(
3597 div()
3598 .flex()
3599 .flex_none()
3600 .overflow_hidden()
3601 .child(self.left_dock.clone()),
3602 )
3603 // Panes
3604 .child(
3605 div()
3606 .flex()
3607 .flex_col()
3608 .flex_1()
3609 .child(self.center.render(
3610 &self.project,
3611 &self.follower_states,
3612 self.active_call(),
3613 &self.active_pane,
3614 self.zoomed.as_ref(),
3615 &self.app_state,
3616 cx,
3617 ))
3618 .child(self.bottom_dock.clone()),
3619 )
3620 // Right Dock
3621 .child(
3622 div()
3623 .flex()
3624 .flex_none()
3625 .overflow_hidden()
3626 .child(self.right_dock.clone()),
3627 ),
3628 )
3629 .children(self.render_notifications(cx)),
3630 )
3631 .child(self.status_bar.clone())
3632 }
3633}
3634
3635// impl View for Workspace {
3636
3637// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3638// let theme = theme::current(cx).clone();
3639// Stack::new()
3640// .with_child(
3641// Flex::column()
3642// .with_child(self.render_titlebar(&theme, cx))
3643// .with_child(
3644// Stack::new()
3645// .with_child({
3646// let project = self.project.clone();
3647// Flex::row()
3648// .with_children(self.render_dock(DockPosition::Left, cx))
3649// .with_child(
3650// Flex::column()
3651// .with_child(
3652// FlexItem::new(
3653// self.center.render(
3654// &project,
3655// &theme,
3656// &self.follower_states,
3657// self.active_call(),
3658// self.active_pane(),
3659// self.zoomed
3660// .as_ref()
3661// .and_then(|zoomed| zoomed.upgrade(cx))
3662// .as_ref(),
3663// &self.app_state,
3664// cx,
3665// ),
3666// )
3667// .flex(1., true),
3668// )
3669// .with_children(
3670// self.render_dock(DockPosition::Bottom, cx),
3671// )
3672// .flex(1., true),
3673// )
3674// .with_children(self.render_dock(DockPosition::Right, cx))
3675// })
3676// .with_child(Overlay::new(
3677// Stack::new()
3678// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3679// enum ZoomBackground {}
3680// let zoomed = zoomed.upgrade(cx)?;
3681
3682// let mut foreground_style =
3683// theme.workspace.zoomed_pane_foreground;
3684// if let Some(zoomed_dock_position) = self.zoomed_position {
3685// foreground_style =
3686// theme.workspace.zoomed_panel_foreground;
3687// let margin = foreground_style.margin.top;
3688// let border = foreground_style.border.top;
3689
3690// // Only include a margin and border on the opposite side.
3691// foreground_style.margin.top = 0.;
3692// foreground_style.margin.left = 0.;
3693// foreground_style.margin.bottom = 0.;
3694// foreground_style.margin.right = 0.;
3695// foreground_style.border.top = false;
3696// foreground_style.border.left = false;
3697// foreground_style.border.bottom = false;
3698// foreground_style.border.right = false;
3699// match zoomed_dock_position {
3700// DockPosition::Left => {
3701// foreground_style.margin.right = margin;
3702// foreground_style.border.right = border;
3703// }
3704// DockPosition::Right => {
3705// foreground_style.margin.left = margin;
3706// foreground_style.border.left = border;
3707// }
3708// DockPosition::Bottom => {
3709// foreground_style.margin.top = margin;
3710// foreground_style.border.top = border;
3711// }
3712// }
3713// }
3714
3715// Some(
3716// ChildView::new(&zoomed, cx)
3717// .contained()
3718// .with_style(foreground_style)
3719// .aligned()
3720// .contained()
3721// .with_style(theme.workspace.zoomed_background)
3722// .mouse::<ZoomBackground>(0)
3723// .capture_all()
3724// .on_down(
3725// MouseButton::Left,
3726// |_, this: &mut Self, cx| {
3727// this.zoom_out(cx);
3728// },
3729// ),
3730// )
3731// }))
3732// .with_children(self.modal.as_ref().map(|modal| {
3733// // Prevent clicks within the modal from falling
3734// // through to the rest of the workspace.
3735// enum ModalBackground {}
3736// MouseEventHandler::new::<ModalBackground, _>(
3737// 0,
3738// cx,
3739// |_, cx| ChildView::new(modal.view.as_any(), cx),
3740// )
3741// .on_click(MouseButton::Left, |_, _, _| {})
3742// .contained()
3743// .with_style(theme.workspace.modal)
3744// .aligned()
3745// .top()
3746// }))
3747// .with_children(self.render_notifications(&theme.workspace, cx)),
3748// ))
3749// .provide_resize_bounds::<WorkspaceBounds>()
3750// .flex(1.0, true),
3751// )
3752// .with_child(ChildView::new(&self.status_bar, cx))
3753// .contained()
3754// .with_background_color(theme.workspace.background),
3755// )
3756// .with_children(DragAndDrop::render(cx))
3757// .with_children(self.render_disconnected_overlay(cx))
3758// .into_any_named("workspace")
3759// }
3760
3761// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3762// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3763// }
3764// }
3765
3766impl WorkspaceStore {
3767 pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
3768 Self {
3769 workspaces: Default::default(),
3770 followers: Default::default(),
3771 _subscriptions: vec![
3772 client.add_request_handler(cx.weak_model(), Self::handle_follow),
3773 client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3774 client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3775 ],
3776 client,
3777 }
3778 }
3779
3780 pub fn update_followers(
3781 &self,
3782 project_id: Option<u64>,
3783 update: proto::update_followers::Variant,
3784 cx: &AppContext,
3785 ) -> Option<()> {
3786 if !cx.has_global::<Model<ActiveCall>>() {
3787 return None;
3788 }
3789
3790 let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3791 let follower_ids: Vec<_> = self
3792 .followers
3793 .iter()
3794 .filter_map(|follower| {
3795 if follower.project_id == project_id || project_id.is_none() {
3796 Some(follower.peer_id.into())
3797 } else {
3798 None
3799 }
3800 })
3801 .collect();
3802 if follower_ids.is_empty() {
3803 return None;
3804 }
3805 self.client
3806 .send(proto::UpdateFollowers {
3807 room_id,
3808 project_id,
3809 follower_ids,
3810 variant: Some(update),
3811 })
3812 .log_err()
3813 }
3814
3815 pub async fn handle_follow(
3816 this: Model<Self>,
3817 envelope: TypedEnvelope<proto::Follow>,
3818 _: Arc<Client>,
3819 mut cx: AsyncAppContext,
3820 ) -> Result<proto::FollowResponse> {
3821 this.update(&mut cx, |this, cx| {
3822 let follower = Follower {
3823 project_id: envelope.payload.project_id,
3824 peer_id: envelope.original_sender_id()?,
3825 };
3826 let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3827
3828 let mut response = proto::FollowResponse::default();
3829 for workspace in &this.workspaces {
3830 workspace
3831 .update(cx, |workspace, cx| {
3832 let handler_response = workspace.handle_follow(follower.project_id, cx);
3833 if response.views.is_empty() {
3834 response.views = handler_response.views;
3835 } else {
3836 response.views.extend_from_slice(&handler_response.views);
3837 }
3838
3839 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3840 if response.active_view_id.is_none()
3841 || Some(workspace.project.downgrade()) == active_project
3842 {
3843 response.active_view_id = Some(active_view_id);
3844 }
3845 }
3846 })
3847 .ok();
3848 }
3849
3850 if let Err(ix) = this.followers.binary_search(&follower) {
3851 this.followers.insert(ix, follower);
3852 }
3853
3854 Ok(response)
3855 })?
3856 }
3857
3858 async fn handle_unfollow(
3859 model: Model<Self>,
3860 envelope: TypedEnvelope<proto::Unfollow>,
3861 _: Arc<Client>,
3862 mut cx: AsyncAppContext,
3863 ) -> Result<()> {
3864 model.update(&mut cx, |this, _| {
3865 let follower = Follower {
3866 project_id: envelope.payload.project_id,
3867 peer_id: envelope.original_sender_id()?,
3868 };
3869 if let Ok(ix) = this.followers.binary_search(&follower) {
3870 this.followers.remove(ix);
3871 }
3872 Ok(())
3873 })?
3874 }
3875
3876 async fn handle_update_followers(
3877 this: Model<Self>,
3878 envelope: TypedEnvelope<proto::UpdateFollowers>,
3879 _: Arc<Client>,
3880 mut cx: AsyncAppContext,
3881 ) -> Result<()> {
3882 let leader_id = envelope.original_sender_id()?;
3883 let update = envelope.payload;
3884
3885 dbg!("handle_upate_followers");
3886
3887 this.update(&mut cx, |this, cx| {
3888 for workspace in &this.workspaces {
3889 workspace.update(cx, |workspace, cx| {
3890 let project_id = workspace.project.read(cx).remote_id();
3891 if update.project_id != project_id && update.project_id.is_some() {
3892 return;
3893 }
3894 workspace.handle_update_followers(leader_id, update.clone(), cx);
3895 })?;
3896 }
3897 Ok(())
3898 })?
3899 }
3900}
3901
3902impl ViewId {
3903 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3904 Ok(Self {
3905 creator: message
3906 .creator
3907 .ok_or_else(|| anyhow!("creator is missing"))?,
3908 id: message.id,
3909 })
3910 }
3911
3912 pub(crate) fn to_proto(&self) -> proto::ViewId {
3913 proto::ViewId {
3914 creator: Some(self.creator),
3915 id: self.id,
3916 }
3917 }
3918}
3919
3920pub trait WorkspaceHandle {
3921 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3922}
3923
3924impl WorkspaceHandle for View<Workspace> {
3925 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3926 self.read(cx)
3927 .worktrees(cx)
3928 .flat_map(|worktree| {
3929 let worktree_id = worktree.read(cx).id();
3930 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3931 worktree_id,
3932 path: f.path.clone(),
3933 })
3934 })
3935 .collect::<Vec<_>>()
3936 }
3937}
3938
3939impl std::fmt::Debug for OpenPaths {
3940 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3941 f.debug_struct("OpenPaths")
3942 .field("paths", &self.paths)
3943 .finish()
3944 }
3945}
3946
3947pub fn activate_workspace_for_project(
3948 cx: &mut AppContext,
3949 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
3950) -> Option<WindowHandle<Workspace>> {
3951 for window in cx.windows() {
3952 let Some(workspace) = window.downcast::<Workspace>() else {
3953 continue;
3954 };
3955
3956 let predicate = workspace
3957 .update(cx, |workspace, cx| {
3958 let project = workspace.project.read(cx);
3959 if predicate(project, cx) {
3960 cx.activate_window();
3961 true
3962 } else {
3963 false
3964 }
3965 })
3966 .log_err()
3967 .unwrap_or(false);
3968
3969 if predicate {
3970 return Some(workspace);
3971 }
3972 }
3973
3974 None
3975}
3976
3977pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3978 DB.last_workspace().await.log_err().flatten()
3979}
3980
3981async fn join_channel_internal(
3982 channel_id: u64,
3983 app_state: &Arc<AppState>,
3984 requesting_window: Option<WindowHandle<Workspace>>,
3985 active_call: &Model<ActiveCall>,
3986 cx: &mut AsyncAppContext,
3987) -> Result<bool> {
3988 let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
3989 let Some(room) = active_call.room().map(|room| room.read(cx)) else {
3990 return (false, None);
3991 };
3992
3993 let already_in_channel = room.channel_id() == Some(channel_id);
3994 let should_prompt = room.is_sharing_project()
3995 && room.remote_participants().len() > 0
3996 && !already_in_channel;
3997 let open_room = if already_in_channel {
3998 active_call.room().cloned()
3999 } else {
4000 None
4001 };
4002 (should_prompt, open_room)
4003 })?;
4004
4005 if let Some(room) = open_room {
4006 let task = room.update(cx, |room, cx| {
4007 if let Some((project, host)) = room.most_active_project(cx) {
4008 return Some(join_remote_project(project, host, app_state.clone(), cx));
4009 }
4010
4011 None
4012 })?;
4013 if let Some(task) = task {
4014 task.await?;
4015 }
4016 return anyhow::Ok(true);
4017 }
4018
4019 if should_prompt {
4020 if let Some(workspace) = requesting_window {
4021 let answer = workspace.update(cx, |_, cx| {
4022 cx.prompt(
4023 PromptLevel::Warning,
4024 "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4025 &["Yes, Join Channel", "Cancel"],
4026 )
4027 })?.await;
4028
4029 if answer == Ok(1) {
4030 return Ok(false);
4031 }
4032 } else {
4033 return Ok(false); // unreachable!() hopefully
4034 }
4035 }
4036
4037 let client = cx.update(|cx| active_call.read(cx).client())?;
4038
4039 let mut client_status = client.status();
4040
4041 // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4042 'outer: loop {
4043 let Some(status) = client_status.recv().await else {
4044 return Err(anyhow!("error connecting"));
4045 };
4046
4047 match status {
4048 Status::Connecting
4049 | Status::Authenticating
4050 | Status::Reconnecting
4051 | Status::Reauthenticating => continue,
4052 Status::Connected { .. } => break 'outer,
4053 Status::SignedOut => return Err(anyhow!("not signed in")),
4054 Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4055 Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4056 return Err(anyhow!("zed is offline"))
4057 }
4058 }
4059 }
4060
4061 let room = active_call
4062 .update(cx, |active_call, cx| {
4063 active_call.join_channel(channel_id, cx)
4064 })?
4065 .await?;
4066
4067 let Some(room) = room else {
4068 return anyhow::Ok(true);
4069 };
4070
4071 room.update(cx, |room, _| room.room_update_completed())?
4072 .await;
4073
4074 let task = room.update(cx, |room, cx| {
4075 if let Some((project, host)) = room.most_active_project(cx) {
4076 return Some(join_remote_project(project, host, app_state.clone(), cx));
4077 }
4078
4079 None
4080 })?;
4081 if let Some(task) = task {
4082 task.await?;
4083 return anyhow::Ok(true);
4084 }
4085 anyhow::Ok(false)
4086}
4087
4088pub fn join_channel(
4089 channel_id: u64,
4090 app_state: Arc<AppState>,
4091 requesting_window: Option<WindowHandle<Workspace>>,
4092 cx: &mut AppContext,
4093) -> Task<Result<()>> {
4094 let active_call = ActiveCall::global(cx);
4095 cx.spawn(|mut cx| async move {
4096 let result = join_channel_internal(
4097 channel_id,
4098 &app_state,
4099 requesting_window,
4100 &active_call,
4101 &mut cx,
4102 )
4103 .await;
4104
4105 // join channel succeeded, and opened a window
4106 if matches!(result, Ok(true)) {
4107 return anyhow::Ok(());
4108 }
4109
4110 if requesting_window.is_some() {
4111 return anyhow::Ok(());
4112 }
4113
4114 // find an existing workspace to focus and show call controls
4115 let mut active_window = activate_any_workspace_window(&mut cx);
4116 if active_window.is_none() {
4117 // no open workspaces, make one to show the error in (blergh)
4118 cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
4119 .await?;
4120 }
4121
4122 active_window = activate_any_workspace_window(&mut cx);
4123 let Some(active_window) = active_window else {
4124 return anyhow::Ok(());
4125 };
4126
4127 if let Err(err) = result {
4128 active_window
4129 .update(&mut cx, |_, cx| {
4130 cx.prompt(
4131 PromptLevel::Critical,
4132 &format!("Failed to join channel: {}", err),
4133 &["Ok"],
4134 )
4135 })?
4136 .await
4137 .ok();
4138 }
4139
4140 // return ok, we showed the error to the user.
4141 return anyhow::Ok(());
4142 })
4143}
4144
4145pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4146 cx.update(|cx| {
4147 for window in cx.windows() {
4148 let is_workspace = window.downcast::<Workspace>().is_some();
4149 if is_workspace {
4150 window.update(cx, |_, cx| cx.activate_window()).ok();
4151 return Some(window);
4152 }
4153 }
4154 None
4155 })
4156 .ok()
4157 .flatten()
4158}
4159
4160#[allow(clippy::type_complexity)]
4161pub fn open_paths(
4162 abs_paths: &[PathBuf],
4163 app_state: &Arc<AppState>,
4164 requesting_window: Option<WindowHandle<Workspace>>,
4165 cx: &mut AppContext,
4166) -> Task<
4167 anyhow::Result<(
4168 WindowHandle<Workspace>,
4169 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4170 )>,
4171> {
4172 let app_state = app_state.clone();
4173 let abs_paths = abs_paths.to_vec();
4174 // Open paths in existing workspace if possible
4175 let existing = activate_workspace_for_project(cx, {
4176 let abs_paths = abs_paths.clone();
4177 move |project, cx| project.contains_paths(&abs_paths, cx)
4178 });
4179 cx.spawn(move |mut cx| async move {
4180 if let Some(existing) = existing {
4181 // // Ok((
4182 // existing.clone(),
4183 // cx.update_window_root(&existing, |workspace, cx| {
4184 // workspace.open_paths(abs_paths, true, cx)
4185 // })?
4186 // .await,
4187 // ))
4188 todo!()
4189 } else {
4190 cx.update(move |cx| {
4191 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4192 })?
4193 .await
4194 }
4195 })
4196}
4197
4198pub fn open_new(
4199 app_state: &Arc<AppState>,
4200 cx: &mut AppContext,
4201 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4202) -> Task<()> {
4203 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4204 cx.spawn(|mut cx| async move {
4205 if let Some((workspace, opened_paths)) = task.await.log_err() {
4206 workspace
4207 .update(&mut cx, |workspace, cx| {
4208 if opened_paths.is_empty() {
4209 init(workspace, cx)
4210 }
4211 })
4212 .log_err();
4213 }
4214 })
4215}
4216
4217pub fn create_and_open_local_file(
4218 path: &'static Path,
4219 cx: &mut ViewContext<Workspace>,
4220 default_content: impl 'static + Send + FnOnce() -> Rope,
4221) -> Task<Result<Box<dyn ItemHandle>>> {
4222 cx.spawn(|workspace, mut cx| async move {
4223 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4224 if !fs.is_file(path).await {
4225 fs.create_file(path, Default::default()).await?;
4226 fs.save(path, &default_content(), Default::default())
4227 .await?;
4228 }
4229
4230 let mut items = workspace
4231 .update(&mut cx, |workspace, cx| {
4232 workspace.with_local_workspace(cx, |workspace, cx| {
4233 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4234 })
4235 })?
4236 .await?
4237 .await;
4238
4239 let item = items.pop().flatten();
4240 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4241 })
4242}
4243
4244pub fn join_remote_project(
4245 project_id: u64,
4246 follow_user_id: u64,
4247 app_state: Arc<AppState>,
4248 cx: &mut AppContext,
4249) -> Task<Result<()>> {
4250 let windows = cx.windows();
4251 cx.spawn(|mut cx| async move {
4252 let existing_workspace = windows.into_iter().find_map(|window| {
4253 window.downcast::<Workspace>().and_then(|window| {
4254 window
4255 .update(&mut cx, |workspace, cx| {
4256 if workspace.project().read(cx).remote_id() == Some(project_id) {
4257 Some(window)
4258 } else {
4259 None
4260 }
4261 })
4262 .unwrap_or(None)
4263 })
4264 });
4265
4266 let workspace = if let Some(existing_workspace) = existing_workspace {
4267 existing_workspace
4268 } else {
4269 let active_call = cx.update(|cx| ActiveCall::global(cx))?;
4270 let room = active_call
4271 .read_with(&cx, |call, _| call.room().cloned())?
4272 .ok_or_else(|| anyhow!("not in a call"))?;
4273 let project = room
4274 .update(&mut cx, |room, cx| {
4275 room.join_project(
4276 project_id,
4277 app_state.languages.clone(),
4278 app_state.fs.clone(),
4279 cx,
4280 )
4281 })?
4282 .await?;
4283
4284 let window_bounds_override = window_bounds_env_override(&cx);
4285 cx.update(|cx| {
4286 let options = (app_state.build_window_options)(window_bounds_override, None, cx);
4287 cx.open_window(options, |cx| {
4288 cx.build_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
4289 })
4290 })?
4291 };
4292
4293 workspace.update(&mut cx, |workspace, cx| {
4294 cx.activate(true);
4295 cx.activate_window();
4296
4297 if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4298 let follow_peer_id = room
4299 .read(cx)
4300 .remote_participants()
4301 .iter()
4302 .find(|(_, participant)| participant.user.id == follow_user_id)
4303 .map(|(_, p)| p.peer_id)
4304 .or_else(|| {
4305 // If we couldn't follow the given user, follow the host instead.
4306 let collaborator = workspace
4307 .project()
4308 .read(cx)
4309 .collaborators()
4310 .values()
4311 .find(|collaborator| collaborator.replica_id == 0)?;
4312 Some(collaborator.peer_id)
4313 });
4314
4315 if let Some(follow_peer_id) = follow_peer_id {
4316 workspace
4317 .follow(follow_peer_id, cx)
4318 .map(|follow| follow.detach_and_log_err(cx));
4319 }
4320 }
4321 })?;
4322
4323 anyhow::Ok(())
4324 })
4325}
4326
4327pub fn restart(_: &Restart, cx: &mut AppContext) {
4328 let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
4329 let mut workspace_windows = cx
4330 .windows()
4331 .into_iter()
4332 .filter_map(|window| window.downcast::<Workspace>())
4333 .collect::<Vec<_>>();
4334
4335 // If multiple windows have unsaved changes, and need a save prompt,
4336 // prompt in the active window before switching to a different window.
4337 workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4338
4339 let mut prompt = None;
4340 if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4341 prompt = window
4342 .update(cx, |_, cx| {
4343 cx.prompt(
4344 PromptLevel::Info,
4345 "Are you sure you want to restart?",
4346 &["Restart", "Cancel"],
4347 )
4348 })
4349 .ok();
4350 }
4351
4352 cx.spawn(|mut cx| async move {
4353 if let Some(mut prompt) = prompt {
4354 let answer = prompt.await?;
4355 if answer != 0 {
4356 return Ok(());
4357 }
4358 }
4359
4360 // If the user cancels any save prompt, then keep the app open.
4361 for window in workspace_windows {
4362 if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
4363 workspace.prepare_to_close(true, cx)
4364 }) {
4365 if !should_close.await? {
4366 return Ok(());
4367 }
4368 }
4369 }
4370
4371 cx.update(|cx| cx.restart())
4372 })
4373 .detach_and_log_err(cx);
4374}
4375
4376fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4377 let mut parts = value.split(',');
4378 let x: usize = parts.next()?.parse().ok()?;
4379 let y: usize = parts.next()?.parse().ok()?;
4380 Some(point((x as f64).into(), (y as f64).into()))
4381}
4382
4383fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4384 let mut parts = value.split(',');
4385 let width: usize = parts.next()?.parse().ok()?;
4386 let height: usize = parts.next()?.parse().ok()?;
4387 Some(size((width as f64).into(), (height as f64).into()))
4388}
4389
4390#[cfg(test)]
4391mod tests {
4392 use super::*;
4393 use crate::item::{
4394 test::{TestItem, TestProjectItem},
4395 ItemEvent,
4396 };
4397 use fs::FakeFs;
4398 use gpui::TestAppContext;
4399 use project::{Project, ProjectEntryId};
4400 use serde_json::json;
4401 use settings::SettingsStore;
4402 use std::{cell::RefCell, rc::Rc};
4403
4404 #[gpui::test]
4405 async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4406 init_test(cx);
4407
4408 let fs = FakeFs::new(cx.executor());
4409 let project = Project::test(fs, [], cx).await;
4410 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4411
4412 // Adding an item with no ambiguity renders the tab without detail.
4413 let item1 = cx.build_view(|cx| {
4414 let mut item = TestItem::new(cx);
4415 item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4416 item
4417 });
4418 workspace.update(cx, |workspace, cx| {
4419 workspace.add_item(Box::new(item1.clone()), cx);
4420 });
4421 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
4422
4423 // Adding an item that creates ambiguity increases the level of detail on
4424 // both tabs.
4425 let item2 = cx.build_view(|cx| {
4426 let mut item = TestItem::new(cx);
4427 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4428 item
4429 });
4430 workspace.update(cx, |workspace, cx| {
4431 workspace.add_item(Box::new(item2.clone()), cx);
4432 });
4433 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4434 item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4435
4436 // Adding an item that creates ambiguity increases the level of detail only
4437 // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4438 // we stop at the highest detail available.
4439 let item3 = cx.build_view(|cx| {
4440 let mut item = TestItem::new(cx);
4441 item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4442 item
4443 });
4444 workspace.update(cx, |workspace, cx| {
4445 workspace.add_item(Box::new(item3.clone()), cx);
4446 });
4447 item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4448 item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4449 item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4450 }
4451
4452 #[gpui::test]
4453 async fn test_tracking_active_path(cx: &mut TestAppContext) {
4454 init_test(cx);
4455
4456 let fs = FakeFs::new(cx.executor());
4457 fs.insert_tree(
4458 "/root1",
4459 json!({
4460 "one.txt": "",
4461 "two.txt": "",
4462 }),
4463 )
4464 .await;
4465 fs.insert_tree(
4466 "/root2",
4467 json!({
4468 "three.txt": "",
4469 }),
4470 )
4471 .await;
4472
4473 let project = Project::test(fs, ["root1".as_ref()], cx).await;
4474 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4475 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4476 let worktree_id = project.read_with(cx, |project, cx| {
4477 project.worktrees().next().unwrap().read(cx).id()
4478 });
4479
4480 let item1 = cx.build_view(|cx| {
4481 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4482 });
4483 let item2 = cx.build_view(|cx| {
4484 TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4485 });
4486
4487 // Add an item to an empty pane
4488 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4489 project.read_with(cx, |project, cx| {
4490 assert_eq!(
4491 project.active_entry(),
4492 project
4493 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4494 .map(|e| e.id)
4495 );
4496 });
4497 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
4498
4499 // Add a second item to a non-empty pane
4500 workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4501 assert_eq!(cx.window_title().as_deref(), Some("two.txt β root1"));
4502 project.read_with(cx, |project, cx| {
4503 assert_eq!(
4504 project.active_entry(),
4505 project
4506 .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4507 .map(|e| e.id)
4508 );
4509 });
4510
4511 // Close the active item
4512 pane.update(cx, |pane, cx| {
4513 pane.close_active_item(&Default::default(), cx).unwrap()
4514 })
4515 .await
4516 .unwrap();
4517 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
4518 project.read_with(cx, |project, cx| {
4519 assert_eq!(
4520 project.active_entry(),
4521 project
4522 .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4523 .map(|e| e.id)
4524 );
4525 });
4526
4527 // Add a project folder
4528 project
4529 .update(cx, |project, cx| {
4530 project.find_or_create_local_worktree("/root2", true, cx)
4531 })
4532 .await
4533 .unwrap();
4534 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1, root2"));
4535
4536 // Remove a project folder
4537 project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4538 assert_eq!(cx.window_title().as_deref(), Some("one.txt β root2"));
4539 }
4540
4541 #[gpui::test]
4542 async fn test_close_window(cx: &mut TestAppContext) {
4543 init_test(cx);
4544
4545 let fs = FakeFs::new(cx.executor());
4546 fs.insert_tree("/root", json!({ "one": "" })).await;
4547
4548 let project = Project::test(fs, ["root".as_ref()], cx).await;
4549 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
4550
4551 // When there are no dirty items, there's nothing to do.
4552 let item1 = cx.build_view(|cx| TestItem::new(cx));
4553 workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4554 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4555 assert!(task.await.unwrap());
4556
4557 // When there are dirty untitled items, prompt to save each one. If the user
4558 // cancels any prompt, then abort.
4559 let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
4560 let item3 = cx.build_view(|cx| {
4561 TestItem::new(cx)
4562 .with_dirty(true)
4563 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4564 });
4565 workspace.update(cx, |w, cx| {
4566 w.add_item(Box::new(item2.clone()), cx);
4567 w.add_item(Box::new(item3.clone()), cx);
4568 });
4569 let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4570 cx.executor().run_until_parked();
4571 cx.simulate_prompt_answer(2); // cancel save all
4572 cx.executor().run_until_parked();
4573 cx.simulate_prompt_answer(2); // cancel save all
4574 cx.executor().run_until_parked();
4575 assert!(!cx.has_pending_prompt());
4576 assert!(!task.await.unwrap());
4577 }
4578
4579 #[gpui::test]
4580 async fn test_close_pane_items(cx: &mut TestAppContext) {
4581 init_test(cx);
4582
4583 let fs = FakeFs::new(cx.executor());
4584
4585 let project = Project::test(fs, None, cx).await;
4586 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4587
4588 let item1 = cx.build_view(|cx| {
4589 TestItem::new(cx)
4590 .with_dirty(true)
4591 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4592 });
4593 let item2 = cx.build_view(|cx| {
4594 TestItem::new(cx)
4595 .with_dirty(true)
4596 .with_conflict(true)
4597 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4598 });
4599 let item3 = cx.build_view(|cx| {
4600 TestItem::new(cx)
4601 .with_dirty(true)
4602 .with_conflict(true)
4603 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4604 });
4605 let item4 = cx.build_view(|cx| {
4606 TestItem::new(cx)
4607 .with_dirty(true)
4608 .with_project_items(&[TestProjectItem::new_untitled(cx)])
4609 });
4610 let pane = workspace.update(cx, |workspace, cx| {
4611 workspace.add_item(Box::new(item1.clone()), cx);
4612 workspace.add_item(Box::new(item2.clone()), cx);
4613 workspace.add_item(Box::new(item3.clone()), cx);
4614 workspace.add_item(Box::new(item4.clone()), cx);
4615 workspace.active_pane().clone()
4616 });
4617
4618 let close_items = pane.update(cx, |pane, cx| {
4619 pane.activate_item(1, true, true, cx);
4620 assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4621 let item1_id = item1.item_id();
4622 let item3_id = item3.item_id();
4623 let item4_id = item4.item_id();
4624 pane.close_items(cx, SaveIntent::Close, move |id| {
4625 [item1_id, item3_id, item4_id].contains(&id)
4626 })
4627 });
4628 cx.executor().run_until_parked();
4629
4630 assert!(cx.has_pending_prompt());
4631 // Ignore "Save all" prompt
4632 cx.simulate_prompt_answer(2);
4633 cx.executor().run_until_parked();
4634 // There's a prompt to save item 1.
4635 pane.update(cx, |pane, _| {
4636 assert_eq!(pane.items_len(), 4);
4637 assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
4638 });
4639 // Confirm saving item 1.
4640 cx.simulate_prompt_answer(0);
4641 cx.executor().run_until_parked();
4642
4643 // Item 1 is saved. There's a prompt to save item 3.
4644 pane.update(cx, |pane, cx| {
4645 assert_eq!(item1.read(cx).save_count, 1);
4646 assert_eq!(item1.read(cx).save_as_count, 0);
4647 assert_eq!(item1.read(cx).reload_count, 0);
4648 assert_eq!(pane.items_len(), 3);
4649 assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
4650 });
4651 assert!(cx.has_pending_prompt());
4652
4653 // Cancel saving item 3.
4654 cx.simulate_prompt_answer(1);
4655 cx.executor().run_until_parked();
4656
4657 // Item 3 is reloaded. There's a prompt to save item 4.
4658 pane.update(cx, |pane, cx| {
4659 assert_eq!(item3.read(cx).save_count, 0);
4660 assert_eq!(item3.read(cx).save_as_count, 0);
4661 assert_eq!(item3.read(cx).reload_count, 1);
4662 assert_eq!(pane.items_len(), 2);
4663 assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
4664 });
4665 assert!(cx.has_pending_prompt());
4666
4667 // Confirm saving item 4.
4668 cx.simulate_prompt_answer(0);
4669 cx.executor().run_until_parked();
4670
4671 // There's a prompt for a path for item 4.
4672 cx.simulate_new_path_selection(|_| Some(Default::default()));
4673 close_items.await.unwrap();
4674
4675 // The requested items are closed.
4676 pane.update(cx, |pane, cx| {
4677 assert_eq!(item4.read(cx).save_count, 0);
4678 assert_eq!(item4.read(cx).save_as_count, 1);
4679 assert_eq!(item4.read(cx).reload_count, 0);
4680 assert_eq!(pane.items_len(), 1);
4681 assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
4682 });
4683 }
4684
4685 #[gpui::test]
4686 async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4687 init_test(cx);
4688
4689 let fs = FakeFs::new(cx.executor());
4690 let project = Project::test(fs, [], cx).await;
4691 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4692
4693 // Create several workspace items with single project entries, and two
4694 // workspace items with multiple project entries.
4695 let single_entry_items = (0..=4)
4696 .map(|project_entry_id| {
4697 cx.build_view(|cx| {
4698 TestItem::new(cx)
4699 .with_dirty(true)
4700 .with_project_items(&[TestProjectItem::new(
4701 project_entry_id,
4702 &format!("{project_entry_id}.txt"),
4703 cx,
4704 )])
4705 })
4706 })
4707 .collect::<Vec<_>>();
4708 let item_2_3 = cx.build_view(|cx| {
4709 TestItem::new(cx)
4710 .with_dirty(true)
4711 .with_singleton(false)
4712 .with_project_items(&[
4713 single_entry_items[2].read(cx).project_items[0].clone(),
4714 single_entry_items[3].read(cx).project_items[0].clone(),
4715 ])
4716 });
4717 let item_3_4 = cx.build_view(|cx| {
4718 TestItem::new(cx)
4719 .with_dirty(true)
4720 .with_singleton(false)
4721 .with_project_items(&[
4722 single_entry_items[3].read(cx).project_items[0].clone(),
4723 single_entry_items[4].read(cx).project_items[0].clone(),
4724 ])
4725 });
4726
4727 // Create two panes that contain the following project entries:
4728 // left pane:
4729 // multi-entry items: (2, 3)
4730 // single-entry items: 0, 1, 2, 3, 4
4731 // right pane:
4732 // single-entry items: 1
4733 // multi-entry items: (3, 4)
4734 let left_pane = workspace.update(cx, |workspace, cx| {
4735 let left_pane = workspace.active_pane().clone();
4736 workspace.add_item(Box::new(item_2_3.clone()), cx);
4737 for item in single_entry_items {
4738 workspace.add_item(Box::new(item), cx);
4739 }
4740 left_pane.update(cx, |pane, cx| {
4741 pane.activate_item(2, true, true, cx);
4742 });
4743
4744 let right_pane = workspace
4745 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4746 .unwrap();
4747
4748 right_pane.update(cx, |pane, cx| {
4749 pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
4750 });
4751
4752 left_pane
4753 });
4754
4755 cx.focus_view(&left_pane);
4756
4757 // When closing all of the items in the left pane, we should be prompted twice:
4758 // once for project entry 0, and once for project entry 2. Project entries 1,
4759 // 3, and 4 are all still open in the other paten. After those two
4760 // prompts, the task should complete.
4761
4762 let close = left_pane.update(cx, |pane, cx| {
4763 pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
4764 });
4765 cx.executor().run_until_parked();
4766
4767 // Discard "Save all" prompt
4768 cx.simulate_prompt_answer(2);
4769
4770 cx.executor().run_until_parked();
4771 left_pane.update(cx, |pane, cx| {
4772 assert_eq!(
4773 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4774 &[ProjectEntryId::from_proto(0)]
4775 );
4776 });
4777 cx.simulate_prompt_answer(0);
4778
4779 cx.executor().run_until_parked();
4780 left_pane.update(cx, |pane, cx| {
4781 assert_eq!(
4782 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4783 &[ProjectEntryId::from_proto(2)]
4784 );
4785 });
4786 cx.simulate_prompt_answer(0);
4787
4788 cx.executor().run_until_parked();
4789 close.await.unwrap();
4790 left_pane.update(cx, |pane, _| {
4791 assert_eq!(pane.items_len(), 0);
4792 });
4793 }
4794
4795 #[gpui::test]
4796 async fn test_autosave(cx: &mut gpui::TestAppContext) {
4797 init_test(cx);
4798
4799 let fs = FakeFs::new(cx.executor());
4800 let project = Project::test(fs, [], cx).await;
4801 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4802 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4803
4804 let item = cx.build_view(|cx| {
4805 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4806 });
4807 let item_id = item.entity_id();
4808 workspace.update(cx, |workspace, cx| {
4809 workspace.add_item(Box::new(item.clone()), cx);
4810 });
4811
4812 // Autosave on window change.
4813 item.update(cx, |item, cx| {
4814 cx.update_global(|settings: &mut SettingsStore, cx| {
4815 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4816 settings.autosave = Some(AutosaveSetting::OnWindowChange);
4817 })
4818 });
4819 item.is_dirty = true;
4820 });
4821
4822 // Deactivating the window saves the file.
4823 cx.simulate_deactivation();
4824 cx.executor().run_until_parked();
4825 item.update(cx, |item, _| assert_eq!(item.save_count, 1));
4826
4827 // Autosave on focus change.
4828 item.update(cx, |item, cx| {
4829 cx.focus_self();
4830 cx.update_global(|settings: &mut SettingsStore, cx| {
4831 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4832 settings.autosave = Some(AutosaveSetting::OnFocusChange);
4833 })
4834 });
4835 item.is_dirty = true;
4836 });
4837
4838 // Blurring the item saves the file.
4839 item.update(cx, |_, cx| cx.blur());
4840 cx.executor().run_until_parked();
4841 item.update(cx, |item, _| assert_eq!(item.save_count, 2));
4842
4843 // Deactivating the window still saves the file.
4844 cx.simulate_activation();
4845 item.update(cx, |item, cx| {
4846 cx.focus_self();
4847 item.is_dirty = true;
4848 });
4849 cx.simulate_deactivation();
4850
4851 cx.executor().run_until_parked();
4852 item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4853
4854 // Autosave after delay.
4855 item.update(cx, |item, cx| {
4856 cx.update_global(|settings: &mut SettingsStore, cx| {
4857 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4858 settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4859 })
4860 });
4861 item.is_dirty = true;
4862 cx.emit(ItemEvent::Edit);
4863 });
4864
4865 // Delay hasn't fully expired, so the file is still dirty and unsaved.
4866 cx.executor().advance_clock(Duration::from_millis(250));
4867 item.update(cx, |item, _| assert_eq!(item.save_count, 3));
4868
4869 // After delay expires, the file is saved.
4870 cx.executor().advance_clock(Duration::from_millis(250));
4871 item.update(cx, |item, _| assert_eq!(item.save_count, 4));
4872
4873 // Autosave on focus change, ensuring closing the tab counts as such.
4874 item.update(cx, |item, cx| {
4875 cx.update_global(|settings: &mut SettingsStore, cx| {
4876 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4877 settings.autosave = Some(AutosaveSetting::OnFocusChange);
4878 })
4879 });
4880 item.is_dirty = true;
4881 });
4882
4883 pane.update(cx, |pane, cx| {
4884 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4885 })
4886 .await
4887 .unwrap();
4888 assert!(!cx.has_pending_prompt());
4889 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4890
4891 // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4892 workspace.update(cx, |workspace, cx| {
4893 workspace.add_item(Box::new(item.clone()), cx);
4894 });
4895 item.update(cx, |item, cx| {
4896 item.project_items[0].update(cx, |item, _| {
4897 item.entry_id = None;
4898 });
4899 item.is_dirty = true;
4900 cx.blur();
4901 });
4902 cx.executor().run_until_parked();
4903 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4904
4905 // Ensure autosave is prevented for deleted files also when closing the buffer.
4906 let _close_items = pane.update(cx, |pane, cx| {
4907 pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4908 });
4909 cx.executor().run_until_parked();
4910 assert!(cx.has_pending_prompt());
4911 item.update(cx, |item, _| assert_eq!(item.save_count, 5));
4912 }
4913
4914 #[gpui::test]
4915 async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4916 init_test(cx);
4917
4918 let fs = FakeFs::new(cx.executor());
4919
4920 let project = Project::test(fs, [], cx).await;
4921 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
4922
4923 let item = cx.build_view(|cx| {
4924 TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4925 });
4926 let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4927 let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
4928 let toolbar_notify_count = Rc::new(RefCell::new(0));
4929
4930 workspace.update(cx, |workspace, cx| {
4931 workspace.add_item(Box::new(item.clone()), cx);
4932 let toolbar_notification_count = toolbar_notify_count.clone();
4933 cx.observe(&toolbar, move |_, _, _| {
4934 *toolbar_notification_count.borrow_mut() += 1
4935 })
4936 .detach();
4937 });
4938
4939 pane.update(cx, |pane, _| {
4940 assert!(!pane.can_navigate_backward());
4941 assert!(!pane.can_navigate_forward());
4942 });
4943
4944 item.update(cx, |item, cx| {
4945 item.set_state("one".to_string(), cx);
4946 });
4947
4948 // Toolbar must be notified to re-render the navigation buttons
4949 assert_eq!(*toolbar_notify_count.borrow(), 1);
4950
4951 pane.update(cx, |pane, _| {
4952 assert!(pane.can_navigate_backward());
4953 assert!(!pane.can_navigate_forward());
4954 });
4955
4956 workspace
4957 .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4958 .await
4959 .unwrap();
4960
4961 assert_eq!(*toolbar_notify_count.borrow(), 2);
4962 pane.update(cx, |pane, _| {
4963 assert!(!pane.can_navigate_backward());
4964 assert!(pane.can_navigate_forward());
4965 });
4966 }
4967
4968 // #[gpui::test]
4969 // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4970 // init_test(cx);
4971 // let fs = FakeFs::new(cx.executor());
4972
4973 // let project = Project::test(fs, [], cx).await;
4974 // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4975 // let workspace = window.root(cx);
4976
4977 // let panel = workspace.update(cx, |workspace, cx| {
4978 // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
4979 // workspace.add_panel(panel.clone(), cx);
4980
4981 // workspace
4982 // .right_dock()
4983 // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4984
4985 // panel
4986 // });
4987
4988 // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
4989 // pane.update(cx, |pane, cx| {
4990 // let item = cx.build_view(|_| TestItem::new(cx));
4991 // pane.add_item(Box::new(item), true, true, None, cx);
4992 // });
4993
4994 // // Transfer focus from center to panel
4995 // workspace.update(cx, |workspace, cx| {
4996 // workspace.toggle_panel_focus::<TestPanel>(cx);
4997 // });
4998
4999 // workspace.update(cx, |workspace, cx| {
5000 // assert!(workspace.right_dock().read(cx).is_open());
5001 // assert!(!panel.is_zoomed(cx));
5002 // assert!(panel.has_focus(cx));
5003 // });
5004
5005 // // Transfer focus from panel to center
5006 // workspace.update(cx, |workspace, cx| {
5007 // workspace.toggle_panel_focus::<TestPanel>(cx);
5008 // });
5009
5010 // workspace.update(cx, |workspace, cx| {
5011 // assert!(workspace.right_dock().read(cx).is_open());
5012 // assert!(!panel.is_zoomed(cx));
5013 // assert!(!panel.has_focus(cx));
5014 // });
5015
5016 // // Close the dock
5017 // workspace.update(cx, |workspace, cx| {
5018 // workspace.toggle_dock(DockPosition::Right, cx);
5019 // });
5020
5021 // workspace.update(cx, |workspace, cx| {
5022 // assert!(!workspace.right_dock().read(cx).is_open());
5023 // assert!(!panel.is_zoomed(cx));
5024 // assert!(!panel.has_focus(cx));
5025 // });
5026
5027 // // Open the dock
5028 // workspace.update(cx, |workspace, cx| {
5029 // workspace.toggle_dock(DockPosition::Right, cx);
5030 // });
5031
5032 // workspace.update(cx, |workspace, cx| {
5033 // assert!(workspace.right_dock().read(cx).is_open());
5034 // assert!(!panel.is_zoomed(cx));
5035 // assert!(panel.has_focus(cx));
5036 // });
5037
5038 // // Focus and zoom panel
5039 // panel.update(cx, |panel, cx| {
5040 // cx.focus_self();
5041 // panel.set_zoomed(true, cx)
5042 // });
5043
5044 // workspace.update(cx, |workspace, cx| {
5045 // assert!(workspace.right_dock().read(cx).is_open());
5046 // assert!(panel.is_zoomed(cx));
5047 // assert!(panel.has_focus(cx));
5048 // });
5049
5050 // // Transfer focus to the center closes the dock
5051 // workspace.update(cx, |workspace, cx| {
5052 // workspace.toggle_panel_focus::<TestPanel>(cx);
5053 // });
5054
5055 // workspace.update(cx, |workspace, cx| {
5056 // assert!(!workspace.right_dock().read(cx).is_open());
5057 // assert!(panel.is_zoomed(cx));
5058 // assert!(!panel.has_focus(cx));
5059 // });
5060
5061 // // Transferring focus back to the panel keeps it zoomed
5062 // workspace.update(cx, |workspace, cx| {
5063 // workspace.toggle_panel_focus::<TestPanel>(cx);
5064 // });
5065
5066 // workspace.update(cx, |workspace, cx| {
5067 // assert!(workspace.right_dock().read(cx).is_open());
5068 // assert!(panel.is_zoomed(cx));
5069 // assert!(panel.has_focus(cx));
5070 // });
5071
5072 // // Close the dock while it is zoomed
5073 // workspace.update(cx, |workspace, cx| {
5074 // workspace.toggle_dock(DockPosition::Right, cx)
5075 // });
5076
5077 // workspace.update(cx, |workspace, cx| {
5078 // assert!(!workspace.right_dock().read(cx).is_open());
5079 // assert!(panel.is_zoomed(cx));
5080 // assert!(workspace.zoomed.is_none());
5081 // assert!(!panel.has_focus(cx));
5082 // });
5083
5084 // // Opening the dock, when it's zoomed, retains focus
5085 // workspace.update(cx, |workspace, cx| {
5086 // workspace.toggle_dock(DockPosition::Right, cx)
5087 // });
5088
5089 // workspace.update(cx, |workspace, cx| {
5090 // assert!(workspace.right_dock().read(cx).is_open());
5091 // assert!(panel.is_zoomed(cx));
5092 // assert!(workspace.zoomed.is_some());
5093 // assert!(panel.has_focus(cx));
5094 // });
5095
5096 // // Unzoom and close the panel, zoom the active pane.
5097 // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5098 // workspace.update(cx, |workspace, cx| {
5099 // workspace.toggle_dock(DockPosition::Right, cx)
5100 // });
5101 // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5102
5103 // // Opening a dock unzooms the pane.
5104 // workspace.update(cx, |workspace, cx| {
5105 // workspace.toggle_dock(DockPosition::Right, cx)
5106 // });
5107 // workspace.update(cx, |workspace, cx| {
5108 // let pane = pane.read(cx);
5109 // assert!(!pane.is_zoomed());
5110 // assert!(!pane.has_focus());
5111 // assert!(workspace.right_dock().read(cx).is_open());
5112 // assert!(workspace.zoomed.is_none());
5113 // });
5114 // }
5115
5116 // #[gpui::test]
5117 // async fn test_panels(cx: &mut gpui::TestAppContext) {
5118 // init_test(cx);
5119 // let fs = FakeFs::new(cx.executor());
5120
5121 // let project = Project::test(fs, [], cx).await;
5122 // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5123 // let workspace = window.root(cx);
5124
5125 // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5126 // // Add panel_1 on the left, panel_2 on the right.
5127 // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5128 // workspace.add_panel(panel_1.clone(), cx);
5129 // workspace
5130 // .left_dock()
5131 // .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5132 // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5133 // workspace.add_panel(panel_2.clone(), cx);
5134 // workspace
5135 // .right_dock()
5136 // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5137
5138 // let left_dock = workspace.left_dock();
5139 // assert_eq!(
5140 // left_dock.read(cx).visible_panel().unwrap().id(),
5141 // panel_1.id()
5142 // );
5143 // assert_eq!(
5144 // left_dock.read(cx).active_panel_size(cx).unwrap(),
5145 // panel_1.size(cx)
5146 // );
5147
5148 // left_dock.update(cx, |left_dock, cx| {
5149 // left_dock.resize_active_panel(Some(1337.), cx)
5150 // });
5151 // assert_eq!(
5152 // workspace
5153 // .right_dock()
5154 // .read(cx)
5155 // .visible_panel()
5156 // .unwrap()
5157 // .id(),
5158 // panel_2.id()
5159 // );
5160
5161 // (panel_1, panel_2)
5162 // });
5163
5164 // // Move panel_1 to the right
5165 // panel_1.update(cx, |panel_1, cx| {
5166 // panel_1.set_position(DockPosition::Right, cx)
5167 // });
5168
5169 // workspace.update(cx, |workspace, cx| {
5170 // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5171 // // Since it was the only panel on the left, the left dock should now be closed.
5172 // assert!(!workspace.left_dock().read(cx).is_open());
5173 // assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5174 // let right_dock = workspace.right_dock();
5175 // assert_eq!(
5176 // right_dock.read(cx).visible_panel().unwrap().id(),
5177 // panel_1.id()
5178 // );
5179 // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5180
5181 // // Now we move panel_2Β to the left
5182 // panel_2.set_position(DockPosition::Left, cx);
5183 // });
5184
5185 // workspace.update(cx, |workspace, cx| {
5186 // // Since panel_2 was not visible on the right, we don't open the left dock.
5187 // assert!(!workspace.left_dock().read(cx).is_open());
5188 // // And the right dock is unaffected in it's displaying of panel_1
5189 // assert!(workspace.right_dock().read(cx).is_open());
5190 // assert_eq!(
5191 // workspace
5192 // .right_dock()
5193 // .read(cx)
5194 // .visible_panel()
5195 // .unwrap()
5196 // .id(),
5197 // panel_1.id()
5198 // );
5199 // });
5200
5201 // // Move panel_1 back to the left
5202 // panel_1.update(cx, |panel_1, cx| {
5203 // panel_1.set_position(DockPosition::Left, cx)
5204 // });
5205
5206 // workspace.update(cx, |workspace, cx| {
5207 // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5208 // let left_dock = workspace.left_dock();
5209 // assert!(left_dock.read(cx).is_open());
5210 // assert_eq!(
5211 // left_dock.read(cx).visible_panel().unwrap().id(),
5212 // panel_1.id()
5213 // );
5214 // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5215 // // And right the dock should be closed as it no longer has any panels.
5216 // assert!(!workspace.right_dock().read(cx).is_open());
5217
5218 // // Now we move panel_1 to the bottom
5219 // panel_1.set_position(DockPosition::Bottom, cx);
5220 // });
5221
5222 // workspace.update(cx, |workspace, cx| {
5223 // // Since panel_1 was visible on the left, we close the left dock.
5224 // assert!(!workspace.left_dock().read(cx).is_open());
5225 // // The bottom dock is sized based on the panel's default size,
5226 // // since the panel orientation changed from vertical to horizontal.
5227 // let bottom_dock = workspace.bottom_dock();
5228 // assert_eq!(
5229 // bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5230 // panel_1.size(cx),
5231 // );
5232 // // Close bottom dock and move panel_1 back to the left.
5233 // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5234 // panel_1.set_position(DockPosition::Left, cx);
5235 // });
5236
5237 // // Emit activated event on panel 1
5238 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5239
5240 // // Now the left dock is open and panel_1 is active and focused.
5241 // workspace.update(cx, |workspace, cx| {
5242 // let left_dock = workspace.left_dock();
5243 // assert!(left_dock.read(cx).is_open());
5244 // assert_eq!(
5245 // left_dock.read(cx).visible_panel().unwrap().id(),
5246 // panel_1.id()
5247 // );
5248 // assert!(panel_1.is_focused(cx));
5249 // });
5250
5251 // // Emit closed event on panel 2, which is not active
5252 // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5253
5254 // // Wo don't close the left dock, because panel_2 wasn't the active panel
5255 // workspace.update(cx, |workspace, cx| {
5256 // let left_dock = workspace.left_dock();
5257 // assert!(left_dock.read(cx).is_open());
5258 // assert_eq!(
5259 // left_dock.read(cx).visible_panel().unwrap().id(),
5260 // panel_1.id()
5261 // );
5262 // });
5263
5264 // // Emitting a ZoomIn event shows the panel as zoomed.
5265 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5266 // workspace.update(cx, |workspace, _| {
5267 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5268 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5269 // });
5270
5271 // // Move panel to another dock while it is zoomed
5272 // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5273 // workspace.update(cx, |workspace, _| {
5274 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5275 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5276 // });
5277
5278 // // If focus is transferred to another view that's not a panel or another pane, we still show
5279 // // the panel as zoomed.
5280 // let focus_receiver = cx.build_view(|_| EmptyView);
5281 // focus_receiver.update(cx, |_, cx| cx.focus_self());
5282 // workspace.update(cx, |workspace, _| {
5283 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5284 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5285 // });
5286
5287 // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5288 // workspace.update(cx, |_, cx| cx.focus_self());
5289 // workspace.update(cx, |workspace, _| {
5290 // assert_eq!(workspace.zoomed, None);
5291 // assert_eq!(workspace.zoomed_position, None);
5292 // });
5293
5294 // // If focus is transferred again to another view that's not a panel or a pane, we won't
5295 // // show the panel as zoomed because it wasn't zoomed before.
5296 // focus_receiver.update(cx, |_, cx| cx.focus_self());
5297 // workspace.update(cx, |workspace, _| {
5298 // assert_eq!(workspace.zoomed, None);
5299 // assert_eq!(workspace.zoomed_position, None);
5300 // });
5301
5302 // // When focus is transferred back to the panel, it is zoomed again.
5303 // panel_1.update(cx, |_, cx| cx.focus_self());
5304 // workspace.update(cx, |workspace, _| {
5305 // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5306 // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5307 // });
5308
5309 // // Emitting a ZoomOut event unzooms the panel.
5310 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5311 // workspace.update(cx, |workspace, _| {
5312 // assert_eq!(workspace.zoomed, None);
5313 // assert_eq!(workspace.zoomed_position, None);
5314 // });
5315
5316 // // Emit closed event on panel 1, which is active
5317 // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5318
5319 // // Now the left dock is closed, because panel_1 was the active panel
5320 // workspace.update(cx, |workspace, cx| {
5321 // let right_dock = workspace.right_dock();
5322 // assert!(!right_dock.read(cx).is_open());
5323 // });
5324 // }
5325
5326 pub fn init_test(cx: &mut TestAppContext) {
5327 cx.update(|cx| {
5328 let settings_store = SettingsStore::test(cx);
5329 cx.set_global(settings_store);
5330 theme::init(theme::LoadThemes::JustBase, cx);
5331 language::init(cx);
5332 crate::init_settings(cx);
5333 Project::init_settings(cx);
5334 });
5335 }
5336}