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