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