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, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
35 ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
36 View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
37 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, &mut ViewContext<Self>) -> Div>>,
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, cx: &mut ViewContext<Self>) -> Div {
3208 self.add_workspace_actions_listeners(div, cx)
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(cx.listener(Self::close_inactive_items_and_panes))
3213 .on_action(cx.listener(Self::close_all_items_and_panes))
3214 // cx.add_global_action(Workspace::close_global);
3215 // cx.add_global_action(restart);
3216 .on_action(cx.listener(Self::save_all))
3217 .on_action(cx.listener(Self::add_folder_to_project))
3218 .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
3219 let pane = workspace.active_pane().clone();
3220 workspace.unfollow(&pane, cx);
3221 }))
3222 .on_action(cx.listener(|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(cx.listener(|workspace, _: &SaveAs, cx| {
3228 workspace
3229 .save_active_item(SaveIntent::SaveAs, cx)
3230 .detach_and_log_err(cx);
3231 }))
3232 .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
3233 workspace.activate_previous_pane(cx)
3234 }))
3235 .on_action(
3236 cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
3237 )
3238 .on_action(
3239 cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
3240 workspace.activate_pane_in_direction(action.0, cx)
3241 }),
3242 )
3243 .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
3244 workspace.swap_pane_in_direction(action.0, cx)
3245 }))
3246 .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| {
3247 this.toggle_dock(DockPosition::Left, cx);
3248 }))
3249 .on_action(
3250 cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
3251 workspace.toggle_dock(DockPosition::Right, cx);
3252 }),
3253 )
3254 .on_action(
3255 cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
3256 workspace.toggle_dock(DockPosition::Bottom, cx);
3257 }),
3258 )
3259 .on_action(
3260 cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
3261 workspace.close_all_docks(cx);
3262 }),
3263 )
3264 // cx.add_action(Workspace::activate_pane_at_index);
3265 // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
3266 // workspace.reopen_closed_item(cx).detach();
3267 // });
3268 // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
3269 // workspace
3270 // .go_back(workspace.active_pane().downgrade(), cx)
3271 // .detach();
3272 // });
3273 // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
3274 // workspace
3275 // .go_forward(workspace.active_pane().downgrade(), cx)
3276 // .detach();
3277 // });
3278
3279 // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
3280 // cx.spawn(|workspace, mut cx| async move {
3281 // let err = install_cli::install_cli(&cx)
3282 // .await
3283 // .context("Failed to create CLI symlink");
3284
3285 // workspace.update(&mut cx, |workspace, cx| {
3286 // if matches!(err, Err(_)) {
3287 // err.notify_err(workspace, cx);
3288 // } else {
3289 // workspace.show_notification(1, cx, |cx| {
3290 // cx.build_view(|_| {
3291 // MessageNotification::new("Successfully installed the `zed` binary")
3292 // })
3293 // });
3294 // }
3295 // })
3296 // })
3297 // .detach();
3298 // });
3299 }
3300
3301 #[cfg(any(test, feature = "test-support"))]
3302 pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
3303 use node_runtime::FakeNodeRuntime;
3304
3305 let client = project.read(cx).client();
3306 let user_store = project.read(cx).user_store();
3307
3308 let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
3309 let app_state = Arc::new(AppState {
3310 languages: project.read(cx).languages().clone(),
3311 workspace_store,
3312 client,
3313 user_store,
3314 fs: project.read(cx).fs().clone(),
3315 build_window_options: |_, _, _| Default::default(),
3316 node_runtime: FakeNodeRuntime::new(),
3317 });
3318 let workspace = Self::new(0, project, app_state, cx);
3319 workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
3320 workspace
3321 }
3322
3323 // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3324 // let dock = match position {
3325 // DockPosition::Left => &self.left_dock,
3326 // DockPosition::Right => &self.right_dock,
3327 // DockPosition::Bottom => &self.bottom_dock,
3328 // };
3329 // let active_panel = dock.read(cx).visible_panel()?;
3330 // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3331 // dock.read(cx).render_placeholder(cx)
3332 // } else {
3333 // ChildView::new(dock, cx).into_any()
3334 // };
3335
3336 // Some(
3337 // element
3338 // .constrained()
3339 // .dynamically(move |constraint, _, cx| match position {
3340 // DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3341 // Vector2F::new(20., constraint.min.y()),
3342 // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3343 // ),
3344 // DockPosition::Bottom => SizeConstraint::new(
3345 // Vector2F::new(constraint.min.x(), 20.),
3346 // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3347 // ),
3348 // })
3349 // .into_any(),
3350 // )
3351 // }
3352 // }
3353 pub fn register_action<A: Action>(
3354 &mut self,
3355 callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
3356 ) -> &mut Self {
3357 let callback = Arc::new(callback);
3358
3359 self.workspace_actions.push(Box::new(move |div, cx| {
3360 let callback = callback.clone();
3361 div.on_action(
3362 cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
3363 )
3364 }));
3365 self
3366 }
3367
3368 fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext<Self>) -> Div {
3369 let mut div = div
3370 .on_action(cx.listener(Self::close_inactive_items_and_panes))
3371 .on_action(cx.listener(Self::close_all_items_and_panes))
3372 .on_action(cx.listener(Self::add_folder_to_project))
3373 .on_action(cx.listener(Self::save_all))
3374 .on_action(cx.listener(Self::open));
3375 for action in self.workspace_actions.iter() {
3376 div = (action)(div, cx)
3377 }
3378 div
3379 }
3380
3381 pub fn active_modal<V: ManagedView + 'static>(
3382 &mut self,
3383 cx: &ViewContext<Self>,
3384 ) -> Option<View<V>> {
3385 self.modal_layer.read(cx).active_modal()
3386 }
3387
3388 pub fn toggle_modal<V: ManagedView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
3389 where
3390 B: FnOnce(&mut ViewContext<V>) -> V,
3391 {
3392 self.modal_layer
3393 .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
3394 }
3395}
3396
3397fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3398 let display_origin = cx
3399 .update(|cx| Some(cx.displays().first()?.bounds().origin))
3400 .ok()??;
3401 ZED_WINDOW_POSITION
3402 .zip(*ZED_WINDOW_SIZE)
3403 .map(|(position, size)| {
3404 WindowBounds::Fixed(Bounds {
3405 origin: display_origin + position,
3406 size,
3407 })
3408 })
3409}
3410
3411fn open_items(
3412 serialized_workspace: Option<SerializedWorkspace>,
3413 mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3414 app_state: Arc<AppState>,
3415 cx: &mut ViewContext<Workspace>,
3416) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
3417 let restored_items = serialized_workspace.map(|serialized_workspace| {
3418 Workspace::load_workspace(
3419 serialized_workspace,
3420 project_paths_to_open
3421 .iter()
3422 .map(|(_, project_path)| project_path)
3423 .cloned()
3424 .collect(),
3425 cx,
3426 )
3427 });
3428
3429 cx.spawn(|workspace, mut cx| async move {
3430 let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3431
3432 if let Some(restored_items) = restored_items {
3433 let restored_items = restored_items.await?;
3434
3435 let restored_project_paths = restored_items
3436 .iter()
3437 .filter_map(|item| {
3438 cx.update(|_, cx| item.as_ref()?.project_path(cx))
3439 .ok()
3440 .flatten()
3441 })
3442 .collect::<HashSet<_>>();
3443
3444 for restored_item in restored_items {
3445 opened_items.push(restored_item.map(Ok));
3446 }
3447
3448 project_paths_to_open
3449 .iter_mut()
3450 .for_each(|(_, project_path)| {
3451 if let Some(project_path_to_open) = project_path {
3452 if restored_project_paths.contains(project_path_to_open) {
3453 *project_path = None;
3454 }
3455 }
3456 });
3457 } else {
3458 for _ in 0..project_paths_to_open.len() {
3459 opened_items.push(None);
3460 }
3461 }
3462 assert!(opened_items.len() == project_paths_to_open.len());
3463
3464 let tasks =
3465 project_paths_to_open
3466 .into_iter()
3467 .enumerate()
3468 .map(|(i, (abs_path, project_path))| {
3469 let workspace = workspace.clone();
3470 cx.spawn(|mut cx| {
3471 let fs = app_state.fs.clone();
3472 async move {
3473 let file_project_path = project_path?;
3474 if fs.is_file(&abs_path).await {
3475 Some((
3476 i,
3477 workspace
3478 .update(&mut cx, |workspace, cx| {
3479 workspace.open_path(file_project_path, None, true, cx)
3480 })
3481 .log_err()?
3482 .await,
3483 ))
3484 } else {
3485 None
3486 }
3487 }
3488 })
3489 });
3490
3491 let tasks = tasks.collect::<Vec<_>>();
3492
3493 let tasks = futures::future::join_all(tasks.into_iter());
3494 for maybe_opened_path in tasks.await.into_iter() {
3495 if let Some((i, path_open_result)) = maybe_opened_path {
3496 opened_items[i] = Some(path_open_result);
3497 }
3498 }
3499
3500 Ok(opened_items)
3501 })
3502}
3503
3504// todo!()
3505// fn notify_of_new_dock(workspace: &WeakView<Workspace>, cx: &mut AsyncAppContext) {
3506// const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3507// const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3508// const MESSAGE_ID: usize = 2;
3509
3510// if workspace
3511// .read_with(cx, |workspace, cx| {
3512// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3513// })
3514// .unwrap_or(false)
3515// {
3516// return;
3517// }
3518
3519// if db::kvp::KEY_VALUE_STORE
3520// .read_kvp(NEW_DOCK_HINT_KEY)
3521// .ok()
3522// .flatten()
3523// .is_some()
3524// {
3525// if !workspace
3526// .read_with(cx, |workspace, cx| {
3527// workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3528// })
3529// .unwrap_or(false)
3530// {
3531// cx.update(|cx| {
3532// cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3533// let entry = tracker
3534// .entry(TypeId::of::<MessageNotification>())
3535// .or_default();
3536// if !entry.contains(&MESSAGE_ID) {
3537// entry.push(MESSAGE_ID);
3538// }
3539// });
3540// });
3541// }
3542
3543// return;
3544// }
3545
3546// cx.spawn(|_| async move {
3547// db::kvp::KEY_VALUE_STORE
3548// .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3549// .await
3550// .ok();
3551// })
3552// .detach();
3553
3554// workspace
3555// .update(cx, |workspace, cx| {
3556// workspace.show_notification_once(2, cx, |cx| {
3557// cx.build_view(|_| {
3558// MessageNotification::new_element(|text, _| {
3559// Text::new(
3560// "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3561// text,
3562// )
3563// .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
3564// let code_span_background_color = settings::get::<ThemeSettings>(cx)
3565// .theme
3566// .editor
3567// .document_highlight_read_background;
3568
3569// cx.scene().push_quad(gpui::Quad {
3570// bounds,
3571// background: Some(code_span_background_color),
3572// border: Default::default(),
3573// corner_radii: (2.0).into(),
3574// })
3575// })
3576// .into_any()
3577// })
3578// .with_click_message("Read more about the new panel system")
3579// .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3580// })
3581// })
3582// })
3583// .ok();
3584
3585fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
3586 const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3587
3588 workspace
3589 .update(cx, |workspace, cx| {
3590 if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3591 workspace.show_notification_once(0, cx, |cx| {
3592 cx.build_view(|_| {
3593 MessageNotification::new("Failed to load the database file.")
3594 .with_click_message("Click to let us know about this error")
3595 .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
3596 })
3597 });
3598 }
3599 })
3600 .log_err();
3601}
3602
3603impl FocusableView for Workspace {
3604 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3605 self.active_pane.focus_handle(cx)
3606 }
3607}
3608
3609impl Render for Workspace {
3610 type Element = Div;
3611
3612 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
3613 let mut context = KeyContext::default();
3614 context.add("Workspace");
3615
3616 let (ui_font, ui_font_size) = {
3617 let theme_settings = ThemeSettings::get_global(cx);
3618 (
3619 theme_settings.ui_font.family.clone(),
3620 theme_settings.ui_font_size.clone(),
3621 )
3622 };
3623
3624 cx.set_rem_size(ui_font_size);
3625
3626 self.actions(div(), cx)
3627 .key_context(context)
3628 .relative()
3629 .size_full()
3630 .flex()
3631 .flex_col()
3632 .font(ui_font)
3633 .gap_0()
3634 .justify_start()
3635 .items_start()
3636 .text_color(cx.theme().colors().text)
3637 .bg(cx.theme().colors().background)
3638 .children(self.titlebar_item.clone())
3639 .child(
3640 // todo! should this be a component a view?
3641 div()
3642 .id("workspace")
3643 .relative()
3644 .flex_1()
3645 .w_full()
3646 .flex()
3647 .overflow_hidden()
3648 .border_t()
3649 .border_b()
3650 .border_color(cx.theme().colors().border)
3651 .child(self.modal_layer.clone())
3652 .child(
3653 div()
3654 .flex()
3655 .flex_row()
3656 .flex_1()
3657 .h_full()
3658 // Left Dock
3659 .child(
3660 div()
3661 .flex()
3662 .flex_none()
3663 .overflow_hidden()
3664 .child(self.left_dock.clone()),
3665 )
3666 // Panes
3667 .child(
3668 div()
3669 .flex()
3670 .flex_col()
3671 .flex_1()
3672 .child(self.center.render(
3673 &self.project,
3674 &self.follower_states,
3675 self.active_call(),
3676 &self.active_pane,
3677 self.zoomed.as_ref(),
3678 &self.app_state,
3679 cx,
3680 ))
3681 .child(self.bottom_dock.clone()),
3682 )
3683 // Right Dock
3684 .child(
3685 div()
3686 .flex()
3687 .flex_none()
3688 .overflow_hidden()
3689 .child(self.right_dock.clone()),
3690 ),
3691 ),
3692 )
3693 .child(self.status_bar.clone())
3694 }
3695}
3696
3697// impl View for Workspace {
3698
3699// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3700// let theme = theme::current(cx).clone();
3701// Stack::new()
3702// .with_child(
3703// Flex::column()
3704// .with_child(self.render_titlebar(&theme, cx))
3705// .with_child(
3706// Stack::new()
3707// .with_child({
3708// let project = self.project.clone();
3709// Flex::row()
3710// .with_children(self.render_dock(DockPosition::Left, cx))
3711// .with_child(
3712// Flex::column()
3713// .with_child(
3714// FlexItem::new(
3715// self.center.render(
3716// &project,
3717// &theme,
3718// &self.follower_states,
3719// self.active_call(),
3720// self.active_pane(),
3721// self.zoomed
3722// .as_ref()
3723// .and_then(|zoomed| zoomed.upgrade(cx))
3724// .as_ref(),
3725// &self.app_state,
3726// cx,
3727// ),
3728// )
3729// .flex(1., true),
3730// )
3731// .with_children(
3732// self.render_dock(DockPosition::Bottom, cx),
3733// )
3734// .flex(1., true),
3735// )
3736// .with_children(self.render_dock(DockPosition::Right, cx))
3737// })
3738// .with_child(Overlay::new(
3739// Stack::new()
3740// .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3741// enum ZoomBackground {}
3742// let zoomed = zoomed.upgrade(cx)?;
3743
3744// let mut foreground_style =
3745// theme.workspace.zoomed_pane_foreground;
3746// if let Some(zoomed_dock_position) = self.zoomed_position {
3747// foreground_style =
3748// theme.workspace.zoomed_panel_foreground;
3749// let margin = foreground_style.margin.top;
3750// let border = foreground_style.border.top;
3751
3752// // Only include a margin and border on the opposite side.
3753// foreground_style.margin.top = 0.;
3754// foreground_style.margin.left = 0.;
3755// foreground_style.margin.bottom = 0.;
3756// foreground_style.margin.right = 0.;
3757// foreground_style.border.top = false;
3758// foreground_style.border.left = false;
3759// foreground_style.border.bottom = false;
3760// foreground_style.border.right = false;
3761// match zoomed_dock_position {
3762// DockPosition::Left => {
3763// foreground_style.margin.right = margin;
3764// foreground_style.border.right = border;
3765// }
3766// DockPosition::Right => {
3767// foreground_style.margin.left = margin;
3768// foreground_style.border.left = border;
3769// }
3770// DockPosition::Bottom => {
3771// foreground_style.margin.top = margin;
3772// foreground_style.border.top = border;
3773// }
3774// }
3775// }
3776
3777// Some(
3778// ChildView::new(&zoomed, cx)
3779// .contained()
3780// .with_style(foreground_style)
3781// .aligned()
3782// .contained()
3783// .with_style(theme.workspace.zoomed_background)
3784// .mouse::<ZoomBackground>(0)
3785// .capture_all()
3786// .on_down(
3787// MouseButton::Left,
3788// |_, this: &mut Self, cx| {
3789// this.zoom_out(cx);
3790// },
3791// ),
3792// )
3793// }))
3794// .with_children(self.modal.as_ref().map(|modal| {
3795// // Prevent clicks within the modal from falling
3796// // through to the rest of the workspace.
3797// enum ModalBackground {}
3798// MouseEventHandler::new::<ModalBackground, _>(
3799// 0,
3800// cx,
3801// |_, cx| ChildView::new(modal.view.as_any(), cx),
3802// )
3803// .on_click(MouseButton::Left, |_, _, _| {})
3804// .contained()
3805// .with_style(theme.workspace.modal)
3806// .aligned()
3807// .top()
3808// }))
3809// .with_children(self.render_notifications(&theme.workspace, cx)),
3810// ))
3811// .provide_resize_bounds::<WorkspaceBounds>()
3812// .flex(1.0, true),
3813// )
3814// .with_child(ChildView::new(&self.status_bar, cx))
3815// .contained()
3816// .with_background_color(theme.workspace.background),
3817// )
3818// .with_children(DragAndDrop::render(cx))
3819// .with_children(self.render_disconnected_overlay(cx))
3820// .into_any_named("workspace")
3821// }
3822
3823// fn modifiers_changed(&mut self, e: &ModifiersChangedEvent, cx: &mut ViewContext<Self>) -> bool {
3824// DragAndDrop::<Workspace>::update_modifiers(e.modifiers, cx)
3825// }
3826// }
3827
3828impl WorkspaceStore {
3829 pub fn new(client: Arc<Client>, _cx: &mut ModelContext<Self>) -> Self {
3830 Self {
3831 workspaces: Default::default(),
3832 followers: Default::default(),
3833 _subscriptions: vec![],
3834 // client.add_request_handler(cx.weak_model(), Self::handle_follow),
3835 // client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
3836 // client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
3837 // ],
3838 client,
3839 }
3840 }
3841
3842 pub fn update_followers(
3843 &self,
3844 project_id: Option<u64>,
3845 update: proto::update_followers::Variant,
3846 cx: &AppContext,
3847 ) -> Option<()> {
3848 if !cx.has_global::<Model<ActiveCall>>() {
3849 return None;
3850 }
3851
3852 let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
3853 let follower_ids: Vec<_> = self
3854 .followers
3855 .iter()
3856 .filter_map(|follower| {
3857 if follower.project_id == project_id || project_id.is_none() {
3858 Some(follower.peer_id.into())
3859 } else {
3860 None
3861 }
3862 })
3863 .collect();
3864 if follower_ids.is_empty() {
3865 return None;
3866 }
3867 self.client
3868 .send(proto::UpdateFollowers {
3869 room_id,
3870 project_id,
3871 follower_ids,
3872 variant: Some(update),
3873 })
3874 .log_err()
3875 }
3876
3877 pub async fn handle_follow(
3878 this: Model<Self>,
3879 envelope: TypedEnvelope<proto::Follow>,
3880 _: Arc<Client>,
3881 mut cx: AsyncAppContext,
3882 ) -> Result<proto::FollowResponse> {
3883 this.update(&mut cx, |this, cx| {
3884 let follower = Follower {
3885 project_id: envelope.payload.project_id,
3886 peer_id: envelope.original_sender_id()?,
3887 };
3888 let active_project = ActiveCall::global(cx).read(cx).location().cloned();
3889
3890 let mut response = proto::FollowResponse::default();
3891 for workspace in &this.workspaces {
3892 workspace
3893 .update(cx, |workspace, cx| {
3894 let handler_response = workspace.handle_follow(follower.project_id, cx);
3895 if response.views.is_empty() {
3896 response.views = handler_response.views;
3897 } else {
3898 response.views.extend_from_slice(&handler_response.views);
3899 }
3900
3901 if let Some(active_view_id) = handler_response.active_view_id.clone() {
3902 if response.active_view_id.is_none()
3903 || Some(workspace.project.downgrade()) == active_project
3904 {
3905 response.active_view_id = Some(active_view_id);
3906 }
3907 }
3908 })
3909 .ok();
3910 }
3911
3912 if let Err(ix) = this.followers.binary_search(&follower) {
3913 this.followers.insert(ix, follower);
3914 }
3915
3916 Ok(response)
3917 })?
3918 }
3919
3920 async fn handle_unfollow(
3921 model: Model<Self>,
3922 envelope: TypedEnvelope<proto::Unfollow>,
3923 _: Arc<Client>,
3924 mut cx: AsyncAppContext,
3925 ) -> Result<()> {
3926 model.update(&mut cx, |this, _| {
3927 let follower = Follower {
3928 project_id: envelope.payload.project_id,
3929 peer_id: envelope.original_sender_id()?,
3930 };
3931 if let Ok(ix) = this.followers.binary_search(&follower) {
3932 this.followers.remove(ix);
3933 }
3934 Ok(())
3935 })?
3936 }
3937
3938 async fn handle_update_followers(
3939 this: Model<Self>,
3940 envelope: TypedEnvelope<proto::UpdateFollowers>,
3941 _: Arc<Client>,
3942 mut cx: AsyncWindowContext,
3943 ) -> Result<()> {
3944 let leader_id = envelope.original_sender_id()?;
3945 let update = envelope.payload;
3946
3947 this.update(&mut cx, |this, cx| {
3948 for workspace in &this.workspaces {
3949 workspace.update(cx, |workspace, cx| {
3950 let project_id = workspace.project.read(cx).remote_id();
3951 if update.project_id != project_id && update.project_id.is_some() {
3952 return;
3953 }
3954 workspace.handle_update_followers(leader_id, update.clone(), cx);
3955 })?;
3956 }
3957 Ok(())
3958 })?
3959 }
3960}
3961
3962impl ViewId {
3963 pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3964 Ok(Self {
3965 creator: message
3966 .creator
3967 .ok_or_else(|| anyhow!("creator is missing"))?,
3968 id: message.id,
3969 })
3970 }
3971
3972 pub(crate) fn to_proto(&self) -> proto::ViewId {
3973 proto::ViewId {
3974 creator: Some(self.creator),
3975 id: self.id,
3976 }
3977 }
3978}
3979
3980pub trait WorkspaceHandle {
3981 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3982}
3983
3984impl WorkspaceHandle for View<Workspace> {
3985 fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3986 self.read(cx)
3987 .worktrees(cx)
3988 .flat_map(|worktree| {
3989 let worktree_id = worktree.read(cx).id();
3990 worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3991 worktree_id,
3992 path: f.path.clone(),
3993 })
3994 })
3995 .collect::<Vec<_>>()
3996 }
3997}
3998
3999impl std::fmt::Debug for OpenPaths {
4000 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4001 f.debug_struct("OpenPaths")
4002 .field("paths", &self.paths)
4003 .finish()
4004 }
4005}
4006
4007pub struct WorkspaceCreated(pub WeakView<Workspace>);
4008
4009pub fn activate_workspace_for_project(
4010 cx: &mut AppContext,
4011 predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
4012) -> Option<WindowHandle<Workspace>> {
4013 for window in cx.windows() {
4014 let Some(workspace) = window.downcast::<Workspace>() else {
4015 continue;
4016 };
4017
4018 let predicate = workspace
4019 .update(cx, |workspace, cx| {
4020 let project = workspace.project.read(cx);
4021 if predicate(project, cx) {
4022 cx.activate_window();
4023 true
4024 } else {
4025 false
4026 }
4027 })
4028 .log_err()
4029 .unwrap_or(false);
4030
4031 if predicate {
4032 return Some(workspace);
4033 }
4034 }
4035
4036 None
4037}
4038
4039pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
4040 DB.last_workspace().await.log_err().flatten()
4041}
4042
4043// async fn join_channel_internal(
4044// channel_id: u64,
4045// app_state: &Arc<AppState>,
4046// requesting_window: Option<WindowHandle<Workspace>>,
4047// active_call: &ModelHandle<ActiveCall>,
4048// cx: &mut AsyncAppContext,
4049// ) -> Result<bool> {
4050// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
4051// let Some(room) = active_call.room().map(|room| room.read(cx)) else {
4052// return (false, None);
4053// };
4054
4055// let already_in_channel = room.channel_id() == Some(channel_id);
4056// let should_prompt = room.is_sharing_project()
4057// && room.remote_participants().len() > 0
4058// && !already_in_channel;
4059// let open_room = if already_in_channel {
4060// active_call.room().cloned()
4061// } else {
4062// None
4063// };
4064// (should_prompt, open_room)
4065// });
4066
4067// if let Some(room) = open_room {
4068// let task = room.update(cx, |room, cx| {
4069// if let Some((project, host)) = room.most_active_project(cx) {
4070// return Some(join_remote_project(project, host, app_state.clone(), cx));
4071// }
4072
4073// None
4074// });
4075// if let Some(task) = task {
4076// task.await?;
4077// }
4078// return anyhow::Ok(true);
4079// }
4080
4081// if should_prompt {
4082// if let Some(workspace) = requesting_window {
4083// if let Some(window) = workspace.update(cx, |cx| cx.window()) {
4084// let answer = window.prompt(
4085// PromptLevel::Warning,
4086// "Leaving this call will unshare your current project.\nDo you want to switch channels?",
4087// &["Yes, Join Channel", "Cancel"],
4088// cx,
4089// );
4090
4091// if let Some(mut answer) = answer {
4092// if answer.next().await == Some(1) {
4093// return Ok(false);
4094// }
4095// }
4096// } else {
4097// return Ok(false); // unreachable!() hopefully
4098// }
4099// } else {
4100// return Ok(false); // unreachable!() hopefully
4101// }
4102// }
4103
4104// let client = cx.read(|cx| active_call.read(cx).client());
4105
4106// let mut client_status = client.status();
4107
4108// // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
4109// 'outer: loop {
4110// let Some(status) = client_status.recv().await else {
4111// return Err(anyhow!("error connecting"));
4112// };
4113
4114// match status {
4115// Status::Connecting
4116// | Status::Authenticating
4117// | Status::Reconnecting
4118// | Status::Reauthenticating => continue,
4119// Status::Connected { .. } => break 'outer,
4120// Status::SignedOut => return Err(anyhow!("not signed in")),
4121// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
4122// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
4123// return Err(anyhow!("zed is offline"))
4124// }
4125// }
4126// }
4127
4128// let room = active_call
4129// .update(cx, |active_call, cx| {
4130// active_call.join_channel(channel_id, cx)
4131// })
4132// .await?;
4133
4134// room.update(cx, |room, _| room.room_update_completed())
4135// .await;
4136
4137// let task = room.update(cx, |room, cx| {
4138// if let Some((project, host)) = room.most_active_project(cx) {
4139// return Some(join_remote_project(project, host, app_state.clone(), cx));
4140// }
4141
4142// None
4143// });
4144// if let Some(task) = task {
4145// task.await?;
4146// return anyhow::Ok(true);
4147// }
4148// anyhow::Ok(false)
4149// }
4150
4151// pub fn join_channel(
4152// channel_id: u64,
4153// app_state: Arc<AppState>,
4154// requesting_window: Option<WindowHandle<Workspace>>,
4155// cx: &mut AppContext,
4156// ) -> Task<Result<()>> {
4157// let active_call = ActiveCall::global(cx);
4158// cx.spawn(|mut cx| async move {
4159// let result = join_channel_internal(
4160// channel_id,
4161// &app_state,
4162// requesting_window,
4163// &active_call,
4164// &mut cx,
4165// )
4166// .await;
4167
4168// // join channel succeeded, and opened a window
4169// if matches!(result, Ok(true)) {
4170// return anyhow::Ok(());
4171// }
4172
4173// if requesting_window.is_some() {
4174// return anyhow::Ok(());
4175// }
4176
4177// // find an existing workspace to focus and show call controls
4178// let mut active_window = activate_any_workspace_window(&mut cx);
4179// if active_window.is_none() {
4180// // no open workspaces, make one to show the error in (blergh)
4181// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
4182// .await;
4183// }
4184
4185// active_window = activate_any_workspace_window(&mut cx);
4186// if active_window.is_none() {
4187// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
4188// }
4189
4190// if let Err(err) = result {
4191// let prompt = active_window.unwrap().prompt(
4192// PromptLevel::Critical,
4193// &format!("Failed to join channel: {}", err),
4194// &["Ok"],
4195// &mut cx,
4196// );
4197// if let Some(mut prompt) = prompt {
4198// prompt.next().await;
4199// } else {
4200// return Err(err);
4201// }
4202// }
4203
4204// // return ok, we showed the error to the user.
4205// return anyhow::Ok(());
4206// })
4207// }
4208
4209// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
4210// for window in cx.windows() {
4211// let found = window.update(cx, |cx| {
4212// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
4213// if is_workspace {
4214// cx.activate_window();
4215// }
4216// is_workspace
4217// });
4218// if found == Some(true) {
4219// return Some(window);
4220// }
4221// }
4222// None
4223// }
4224
4225#[allow(clippy::type_complexity)]
4226pub fn open_paths(
4227 abs_paths: &[PathBuf],
4228 app_state: &Arc<AppState>,
4229 requesting_window: Option<WindowHandle<Workspace>>,
4230 cx: &mut AppContext,
4231) -> Task<
4232 anyhow::Result<(
4233 WindowHandle<Workspace>,
4234 Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
4235 )>,
4236> {
4237 let app_state = app_state.clone();
4238 let abs_paths = abs_paths.to_vec();
4239 // Open paths in existing workspace if possible
4240 let existing = activate_workspace_for_project(cx, {
4241 let abs_paths = abs_paths.clone();
4242 move |project, cx| project.contains_paths(&abs_paths, cx)
4243 });
4244 cx.spawn(move |mut cx| async move {
4245 if let Some(existing) = existing {
4246 // // Ok((
4247 // existing.clone(),
4248 // cx.update_window_root(&existing, |workspace, cx| {
4249 // workspace.open_paths(abs_paths, true, cx)
4250 // })?
4251 // .await,
4252 // ))
4253 todo!()
4254 } else {
4255 cx.update(move |cx| {
4256 Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
4257 })?
4258 .await
4259 }
4260 })
4261}
4262
4263pub fn open_new(
4264 app_state: &Arc<AppState>,
4265 cx: &mut AppContext,
4266 init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
4267) -> Task<()> {
4268 let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
4269 cx.spawn(|mut cx| async move {
4270 if let Some((workspace, opened_paths)) = task.await.log_err() {
4271 workspace
4272 .update(&mut cx, |workspace, cx| {
4273 if opened_paths.is_empty() {
4274 init(workspace, cx)
4275 }
4276 })
4277 .log_err();
4278 }
4279 })
4280}
4281
4282pub fn create_and_open_local_file(
4283 path: &'static Path,
4284 cx: &mut ViewContext<Workspace>,
4285 default_content: impl 'static + Send + FnOnce() -> Rope,
4286) -> Task<Result<Box<dyn ItemHandle>>> {
4287 cx.spawn(|workspace, mut cx| async move {
4288 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
4289 if !fs.is_file(path).await {
4290 fs.create_file(path, Default::default()).await?;
4291 fs.save(path, &default_content(), Default::default())
4292 .await?;
4293 }
4294
4295 let mut items = workspace
4296 .update(&mut cx, |workspace, cx| {
4297 workspace.with_local_workspace(cx, |workspace, cx| {
4298 workspace.open_paths(vec![path.to_path_buf()], false, cx)
4299 })
4300 })?
4301 .await?
4302 .await;
4303
4304 let item = items.pop().flatten();
4305 item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
4306 })
4307}
4308
4309// pub fn join_remote_project(
4310// project_id: u64,
4311// follow_user_id: u64,
4312// app_state: Arc<AppState>,
4313// cx: &mut AppContext,
4314// ) -> Task<Result<()>> {
4315// cx.spawn(|mut cx| async move {
4316// let windows = cx.windows();
4317// let existing_workspace = windows.into_iter().find_map(|window| {
4318// window.downcast::<Workspace>().and_then(|window| {
4319// window
4320// .read_root_with(&cx, |workspace, cx| {
4321// if workspace.project().read(cx).remote_id() == Some(project_id) {
4322// Some(cx.handle().downgrade())
4323// } else {
4324// None
4325// }
4326// })
4327// .unwrap_or(None)
4328// })
4329// });
4330
4331// let workspace = if let Some(existing_workspace) = existing_workspace {
4332// existing_workspace
4333// } else {
4334// let active_call = cx.read(ActiveCall::global);
4335// let room = active_call
4336// .read_with(&cx, |call, _| call.room().cloned())
4337// .ok_or_else(|| anyhow!("not in a call"))?;
4338// let project = room
4339// .update(&mut cx, |room, cx| {
4340// room.join_project(
4341// project_id,
4342// app_state.languages.clone(),
4343// app_state.fs.clone(),
4344// cx,
4345// )
4346// })
4347// .await?;
4348
4349// let window_bounds_override = window_bounds_env_override(&cx);
4350// let window = cx.add_window(
4351// (app_state.build_window_options)(
4352// window_bounds_override,
4353// None,
4354// cx.platform().as_ref(),
4355// ),
4356// |cx| Workspace::new(0, project, app_state.clone(), cx),
4357// );
4358// let workspace = window.root(&cx).unwrap();
4359// (app_state.initialize_workspace)(
4360// workspace.downgrade(),
4361// false,
4362// app_state.clone(),
4363// cx.clone(),
4364// )
4365// .await
4366// .log_err();
4367
4368// workspace.downgrade()
4369// };
4370
4371// workspace.window().activate(&mut cx);
4372// cx.platform().activate(true);
4373
4374// workspace.update(&mut cx, |workspace, cx| {
4375// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
4376// let follow_peer_id = room
4377// .read(cx)
4378// .remote_participants()
4379// .iter()
4380// .find(|(_, participant)| participant.user.id == follow_user_id)
4381// .map(|(_, p)| p.peer_id)
4382// .or_else(|| {
4383// // If we couldn't follow the given user, follow the host instead.
4384// let collaborator = workspace
4385// .project()
4386// .read(cx)
4387// .collaborators()
4388// .values()
4389// .find(|collaborator| collaborator.replica_id == 0)?;
4390// Some(collaborator.peer_id)
4391// });
4392
4393// if let Some(follow_peer_id) = follow_peer_id {
4394// workspace
4395// .follow(follow_peer_id, cx)
4396// .map(|follow| follow.detach_and_log_err(cx));
4397// }
4398// }
4399// })?;
4400
4401// anyhow::Ok(())
4402// })
4403// }
4404
4405// pub fn restart(_: &Restart, cx: &mut AppContext) {
4406// let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
4407// cx.spawn(|mut cx| async move {
4408// let mut workspace_windows = cx
4409// .windows()
4410// .into_iter()
4411// .filter_map(|window| window.downcast::<Workspace>())
4412// .collect::<Vec<_>>();
4413
4414// // If multiple windows have unsaved changes, and need a save prompt,
4415// // prompt in the active window before switching to a different window.
4416// workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
4417
4418// if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
4419// let answer = window.prompt(
4420// PromptLevel::Info,
4421// "Are you sure you want to restart?",
4422// &["Restart", "Cancel"],
4423// &mut cx,
4424// );
4425
4426// if let Some(mut answer) = answer {
4427// let answer = answer.next().await;
4428// if answer != Some(0) {
4429// return Ok(());
4430// }
4431// }
4432// }
4433
4434// // If the user cancels any save prompt, then keep the app open.
4435// for window in workspace_windows {
4436// if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
4437// workspace.prepare_to_close(true, cx)
4438// }) {
4439// if !should_close.await? {
4440// return Ok(());
4441// }
4442// }
4443// }
4444// cx.platform().restart();
4445// anyhow::Ok(())
4446// })
4447// .detach_and_log_err(cx);
4448// }
4449
4450fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
4451 let mut parts = value.split(',');
4452 let x: usize = parts.next()?.parse().ok()?;
4453 let y: usize = parts.next()?.parse().ok()?;
4454 Some(point((x as f64).into(), (y as f64).into()))
4455}
4456
4457fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
4458 let mut parts = value.split(',');
4459 let width: usize = parts.next()?.parse().ok()?;
4460 let height: usize = parts.next()?.parse().ok()?;
4461 Some(size((width as f64).into(), (height as f64).into()))
4462}
4463
4464// #[cfg(test)]
4465// mod tests {
4466// use super::*;
4467// use crate::{
4468// dock::test::TestPanel,
4469// item::test::{TestItem, TestItemEvent, TestProjectItem},
4470// };
4471// use fs::FakeFs;
4472// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
4473// use project::{Project, ProjectEntryId};
4474// use serde_json::json;
4475// use settings::SettingsStore;
4476// use std::{cell::RefCell, rc::Rc};
4477
4478// #[gpui::test]
4479// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
4480// init_test(cx);
4481
4482// let fs = FakeFs::new(cx.background());
4483// let project = Project::test(fs, [], cx).await;
4484// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4485// let workspace = window.root(cx);
4486
4487// // Adding an item with no ambiguity renders the tab without detail.
4488// let item1 = window.build_view(cx, |_| {
4489// let mut item = TestItem::new();
4490// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
4491// item
4492// });
4493// workspace.update(cx, |workspace, cx| {
4494// workspace.add_item(Box::new(item1.clone()), cx);
4495// });
4496// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
4497
4498// // Adding an item that creates ambiguity increases the level of detail on
4499// // both tabs.
4500// let item2 = window.build_view(cx, |_| {
4501// let mut item = TestItem::new();
4502// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4503// item
4504// });
4505// workspace.update(cx, |workspace, cx| {
4506// workspace.add_item(Box::new(item2.clone()), cx);
4507// });
4508// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4509// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4510
4511// // Adding an item that creates ambiguity increases the level of detail only
4512// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4513// // we stop at the highest detail available.
4514// let item3 = window.build_view(cx, |_| {
4515// let mut item = TestItem::new();
4516// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4517// item
4518// });
4519// workspace.update(cx, |workspace, cx| {
4520// workspace.add_item(Box::new(item3.clone()), cx);
4521// });
4522// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4523// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4524// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4525// }
4526
4527// #[gpui::test]
4528// async fn test_tracking_active_path(cx: &mut TestAppContext) {
4529// init_test(cx);
4530
4531// let fs = FakeFs::new(cx.background());
4532// fs.insert_tree(
4533// "/root1",
4534// json!({
4535// "one.txt": "",
4536// "two.txt": "",
4537// }),
4538// )
4539// .await;
4540// fs.insert_tree(
4541// "/root2",
4542// json!({
4543// "three.txt": "",
4544// }),
4545// )
4546// .await;
4547
4548// let project = Project::test(fs, ["root1".as_ref()], cx).await;
4549// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4550// let workspace = window.root(cx);
4551// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4552// let worktree_id = project.read_with(cx, |project, cx| {
4553// project.worktrees().next().unwrap().read(cx).id()
4554// });
4555
4556// let item1 = window.build_view(cx, |cx| {
4557// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4558// });
4559// let item2 = window.build_view(cx, |cx| {
4560// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4561// });
4562
4563// // Add an item to an empty pane
4564// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4565// project.read_with(cx, |project, cx| {
4566// assert_eq!(
4567// project.active_entry(),
4568// project
4569// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4570// .map(|e| e.id)
4571// );
4572// });
4573// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4574
4575// // Add a second item to a non-empty pane
4576// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4577// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
4578// project.read_with(cx, |project, cx| {
4579// assert_eq!(
4580// project.active_entry(),
4581// project
4582// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4583// .map(|e| e.id)
4584// );
4585// });
4586
4587// // Close the active item
4588// pane.update(cx, |pane, cx| {
4589// pane.close_active_item(&Default::default(), cx).unwrap()
4590// })
4591// .await
4592// .unwrap();
4593// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
4594// project.read_with(cx, |project, cx| {
4595// assert_eq!(
4596// project.active_entry(),
4597// project
4598// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4599// .map(|e| e.id)
4600// );
4601// });
4602
4603// // Add a project folder
4604// project
4605// .update(cx, |project, cx| {
4606// project.find_or_create_local_worktree("/root2", true, cx)
4607// })
4608// .await
4609// .unwrap();
4610// assert_eq!(
4611// window.current_title(cx).as_deref(),
4612// Some("one.txt β root1, root2")
4613// );
4614
4615// // Remove a project folder
4616// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4617// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
4618// }
4619
4620// #[gpui::test]
4621// async fn test_close_window(cx: &mut TestAppContext) {
4622// init_test(cx);
4623
4624// let fs = FakeFs::new(cx.background());
4625// fs.insert_tree("/root", json!({ "one": "" })).await;
4626
4627// let project = Project::test(fs, ["root".as_ref()], cx).await;
4628// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4629// let workspace = window.root(cx);
4630
4631// // When there are no dirty items, there's nothing to do.
4632// let item1 = window.build_view(cx, |_| TestItem::new());
4633// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4634// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4635// assert!(task.await.unwrap());
4636
4637// // When there are dirty untitled items, prompt to save each one. If the user
4638// // cancels any prompt, then abort.
4639// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
4640// let item3 = window.build_view(cx, |cx| {
4641// TestItem::new()
4642// .with_dirty(true)
4643// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4644// });
4645// workspace.update(cx, |w, cx| {
4646// w.add_item(Box::new(item2.clone()), cx);
4647// w.add_item(Box::new(item3.clone()), cx);
4648// });
4649// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4650// cx.foreground().run_until_parked();
4651// window.simulate_prompt_answer(2, cx); // cancel save all
4652// cx.foreground().run_until_parked();
4653// window.simulate_prompt_answer(2, cx); // cancel save all
4654// cx.foreground().run_until_parked();
4655// assert!(!window.has_pending_prompt(cx));
4656// assert!(!task.await.unwrap());
4657// }
4658
4659// #[gpui::test]
4660// async fn test_close_pane_items(cx: &mut TestAppContext) {
4661// init_test(cx);
4662
4663// let fs = FakeFs::new(cx.background());
4664
4665// let project = Project::test(fs, None, cx).await;
4666// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4667// let workspace = window.root(cx);
4668
4669// let item1 = window.build_view(cx, |cx| {
4670// TestItem::new()
4671// .with_dirty(true)
4672// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4673// });
4674// let item2 = window.build_view(cx, |cx| {
4675// TestItem::new()
4676// .with_dirty(true)
4677// .with_conflict(true)
4678// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4679// });
4680// let item3 = window.build_view(cx, |cx| {
4681// TestItem::new()
4682// .with_dirty(true)
4683// .with_conflict(true)
4684// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4685// });
4686// let item4 = window.build_view(cx, |cx| {
4687// TestItem::new()
4688// .with_dirty(true)
4689// .with_project_items(&[TestProjectItem::new_untitled(cx)])
4690// });
4691// let pane = workspace.update(cx, |workspace, cx| {
4692// workspace.add_item(Box::new(item1.clone()), cx);
4693// workspace.add_item(Box::new(item2.clone()), cx);
4694// workspace.add_item(Box::new(item3.clone()), cx);
4695// workspace.add_item(Box::new(item4.clone()), cx);
4696// workspace.active_pane().clone()
4697// });
4698
4699// let close_items = pane.update(cx, |pane, cx| {
4700// pane.activate_item(1, true, true, cx);
4701// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4702// let item1_id = item1.id();
4703// let item3_id = item3.id();
4704// let item4_id = item4.id();
4705// pane.close_items(cx, SaveIntent::Close, move |id| {
4706// [item1_id, item3_id, item4_id].contains(&id)
4707// })
4708// });
4709// cx.foreground().run_until_parked();
4710
4711// assert!(window.has_pending_prompt(cx));
4712// // Ignore "Save all" prompt
4713// window.simulate_prompt_answer(2, cx);
4714// cx.foreground().run_until_parked();
4715// // There's a prompt to save item 1.
4716// pane.read_with(cx, |pane, _| {
4717// assert_eq!(pane.items_len(), 4);
4718// assert_eq!(pane.active_item().unwrap().id(), item1.id());
4719// });
4720// // Confirm saving item 1.
4721// window.simulate_prompt_answer(0, cx);
4722// cx.foreground().run_until_parked();
4723
4724// // Item 1 is saved. There's a prompt to save item 3.
4725// pane.read_with(cx, |pane, cx| {
4726// assert_eq!(item1.read(cx).save_count, 1);
4727// assert_eq!(item1.read(cx).save_as_count, 0);
4728// assert_eq!(item1.read(cx).reload_count, 0);
4729// assert_eq!(pane.items_len(), 3);
4730// assert_eq!(pane.active_item().unwrap().id(), item3.id());
4731// });
4732// assert!(window.has_pending_prompt(cx));
4733
4734// // Cancel saving item 3.
4735// window.simulate_prompt_answer(1, cx);
4736// cx.foreground().run_until_parked();
4737
4738// // Item 3 is reloaded. There's a prompt to save item 4.
4739// pane.read_with(cx, |pane, cx| {
4740// assert_eq!(item3.read(cx).save_count, 0);
4741// assert_eq!(item3.read(cx).save_as_count, 0);
4742// assert_eq!(item3.read(cx).reload_count, 1);
4743// assert_eq!(pane.items_len(), 2);
4744// assert_eq!(pane.active_item().unwrap().id(), item4.id());
4745// });
4746// assert!(window.has_pending_prompt(cx));
4747
4748// // Confirm saving item 4.
4749// window.simulate_prompt_answer(0, cx);
4750// cx.foreground().run_until_parked();
4751
4752// // There's a prompt for a path for item 4.
4753// cx.simulate_new_path_selection(|_| Some(Default::default()));
4754// close_items.await.unwrap();
4755
4756// // The requested items are closed.
4757// pane.read_with(cx, |pane, cx| {
4758// assert_eq!(item4.read(cx).save_count, 0);
4759// assert_eq!(item4.read(cx).save_as_count, 1);
4760// assert_eq!(item4.read(cx).reload_count, 0);
4761// assert_eq!(pane.items_len(), 1);
4762// assert_eq!(pane.active_item().unwrap().id(), item2.id());
4763// });
4764// }
4765
4766// #[gpui::test]
4767// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4768// init_test(cx);
4769
4770// let fs = FakeFs::new(cx.background());
4771
4772// let project = Project::test(fs, [], cx).await;
4773// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4774// let workspace = window.root(cx);
4775
4776// // Create several workspace items with single project entries, and two
4777// // workspace items with multiple project entries.
4778// let single_entry_items = (0..=4)
4779// .map(|project_entry_id| {
4780// window.build_view(cx, |cx| {
4781// TestItem::new()
4782// .with_dirty(true)
4783// .with_project_items(&[TestProjectItem::new(
4784// project_entry_id,
4785// &format!("{project_entry_id}.txt"),
4786// cx,
4787// )])
4788// })
4789// })
4790// .collect::<Vec<_>>();
4791// let item_2_3 = window.build_view(cx, |cx| {
4792// TestItem::new()
4793// .with_dirty(true)
4794// .with_singleton(false)
4795// .with_project_items(&[
4796// single_entry_items[2].read(cx).project_items[0].clone(),
4797// single_entry_items[3].read(cx).project_items[0].clone(),
4798// ])
4799// });
4800// let item_3_4 = window.build_view(cx, |cx| {
4801// TestItem::new()
4802// .with_dirty(true)
4803// .with_singleton(false)
4804// .with_project_items(&[
4805// single_entry_items[3].read(cx).project_items[0].clone(),
4806// single_entry_items[4].read(cx).project_items[0].clone(),
4807// ])
4808// });
4809
4810// // Create two panes that contain the following project entries:
4811// // left pane:
4812// // multi-entry items: (2, 3)
4813// // single-entry items: 0, 1, 2, 3, 4
4814// // right pane:
4815// // single-entry items: 1
4816// // multi-entry items: (3, 4)
4817// let left_pane = workspace.update(cx, |workspace, cx| {
4818// let left_pane = workspace.active_pane().clone();
4819// workspace.add_item(Box::new(item_2_3.clone()), cx);
4820// for item in single_entry_items {
4821// workspace.add_item(Box::new(item), cx);
4822// }
4823// left_pane.update(cx, |pane, cx| {
4824// pane.activate_item(2, true, true, cx);
4825// });
4826
4827// workspace
4828// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4829// .unwrap();
4830
4831// left_pane
4832// });
4833
4834// //Need to cause an effect flush in order to respect new focus
4835// workspace.update(cx, |workspace, cx| {
4836// workspace.add_item(Box::new(item_3_4.clone()), cx);
4837// cx.focus(&left_pane);
4838// });
4839
4840// // When closing all of the items in the left pane, we should be prompted twice:
4841// // once for project entry 0, and once for project entry 2. After those two
4842// // prompts, the task should complete.
4843
4844// let close = left_pane.update(cx, |pane, cx| {
4845// pane.close_items(cx, SaveIntent::Close, move |_| true)
4846// });
4847// cx.foreground().run_until_parked();
4848// // Discard "Save all" prompt
4849// window.simulate_prompt_answer(2, cx);
4850
4851// cx.foreground().run_until_parked();
4852// left_pane.read_with(cx, |pane, cx| {
4853// assert_eq!(
4854// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4855// &[ProjectEntryId::from_proto(0)]
4856// );
4857// });
4858// window.simulate_prompt_answer(0, cx);
4859
4860// cx.foreground().run_until_parked();
4861// left_pane.read_with(cx, |pane, cx| {
4862// assert_eq!(
4863// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4864// &[ProjectEntryId::from_proto(2)]
4865// );
4866// });
4867// window.simulate_prompt_answer(0, cx);
4868
4869// cx.foreground().run_until_parked();
4870// close.await.unwrap();
4871// left_pane.read_with(cx, |pane, _| {
4872// assert_eq!(pane.items_len(), 0);
4873// });
4874// }
4875
4876// #[gpui::test]
4877// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4878// init_test(cx);
4879
4880// let fs = FakeFs::new(cx.background());
4881
4882// let project = Project::test(fs, [], cx).await;
4883// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
4884// let workspace = window.root(cx);
4885// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4886
4887// let item = window.build_view(cx, |cx| {
4888// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4889// });
4890// let item_id = item.id();
4891// workspace.update(cx, |workspace, cx| {
4892// workspace.add_item(Box::new(item.clone()), cx);
4893// });
4894
4895// // Autosave on window change.
4896// item.update(cx, |item, cx| {
4897// cx.update_global(|settings: &mut SettingsStore, cx| {
4898// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4899// settings.autosave = Some(AutosaveSetting::OnWindowChange);
4900// })
4901// });
4902// item.is_dirty = true;
4903// });
4904
4905// // Deactivating the window saves the file.
4906// window.simulate_deactivation(cx);
4907// deterministic.run_until_parked();
4908// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4909
4910// // Autosave on focus change.
4911// item.update(cx, |item, cx| {
4912// cx.focus_self();
4913// cx.update_global(|settings: &mut SettingsStore, cx| {
4914// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4915// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4916// })
4917// });
4918// item.is_dirty = true;
4919// });
4920
4921// // Blurring the item saves the file.
4922// item.update(cx, |_, cx| cx.blur());
4923// deterministic.run_until_parked();
4924// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4925
4926// // Deactivating the window still saves the file.
4927// window.simulate_activation(cx);
4928// item.update(cx, |item, cx| {
4929// cx.focus_self();
4930// item.is_dirty = true;
4931// });
4932// window.simulate_deactivation(cx);
4933
4934// deterministic.run_until_parked();
4935// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4936
4937// // Autosave after delay.
4938// item.update(cx, |item, cx| {
4939// cx.update_global(|settings: &mut SettingsStore, cx| {
4940// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4941// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4942// })
4943// });
4944// item.is_dirty = true;
4945// cx.emit(TestItemEvent::Edit);
4946// });
4947
4948// // Delay hasn't fully expired, so the file is still dirty and unsaved.
4949// deterministic.advance_clock(Duration::from_millis(250));
4950// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4951
4952// // After delay expires, the file is saved.
4953// deterministic.advance_clock(Duration::from_millis(250));
4954// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4955
4956// // Autosave on focus change, ensuring closing the tab counts as such.
4957// item.update(cx, |item, cx| {
4958// cx.update_global(|settings: &mut SettingsStore, cx| {
4959// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4960// settings.autosave = Some(AutosaveSetting::OnFocusChange);
4961// })
4962// });
4963// item.is_dirty = true;
4964// });
4965
4966// pane.update(cx, |pane, cx| {
4967// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4968// })
4969// .await
4970// .unwrap();
4971// assert!(!window.has_pending_prompt(cx));
4972// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4973
4974// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4975// workspace.update(cx, |workspace, cx| {
4976// workspace.add_item(Box::new(item.clone()), cx);
4977// });
4978// item.update(cx, |item, cx| {
4979// item.project_items[0].update(cx, |item, _| {
4980// item.entry_id = None;
4981// });
4982// item.is_dirty = true;
4983// cx.blur();
4984// });
4985// deterministic.run_until_parked();
4986// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4987
4988// // Ensure autosave is prevented for deleted files also when closing the buffer.
4989// let _close_items = pane.update(cx, |pane, cx| {
4990// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
4991// });
4992// deterministic.run_until_parked();
4993// assert!(window.has_pending_prompt(cx));
4994// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4995// }
4996
4997// #[gpui::test]
4998// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4999// init_test(cx);
5000
5001// let fs = FakeFs::new(cx.background());
5002
5003// let project = Project::test(fs, [], cx).await;
5004// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5005// let workspace = window.root(cx);
5006
5007// let item = window.build_view(cx, |cx| {
5008// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
5009// });
5010// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5011// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
5012// let toolbar_notify_count = Rc::new(RefCell::new(0));
5013
5014// workspace.update(cx, |workspace, cx| {
5015// workspace.add_item(Box::new(item.clone()), cx);
5016// let toolbar_notification_count = toolbar_notify_count.clone();
5017// cx.observe(&toolbar, move |_, _, _| {
5018// *toolbar_notification_count.borrow_mut() += 1
5019// })
5020// .detach();
5021// });
5022
5023// pane.read_with(cx, |pane, _| {
5024// assert!(!pane.can_navigate_backward());
5025// assert!(!pane.can_navigate_forward());
5026// });
5027
5028// item.update(cx, |item, cx| {
5029// item.set_state("one".to_string(), cx);
5030// });
5031
5032// // Toolbar must be notified to re-render the navigation buttons
5033// assert_eq!(*toolbar_notify_count.borrow(), 1);
5034
5035// pane.read_with(cx, |pane, _| {
5036// assert!(pane.can_navigate_backward());
5037// assert!(!pane.can_navigate_forward());
5038// });
5039
5040// workspace
5041// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
5042// .await
5043// .unwrap();
5044
5045// assert_eq!(*toolbar_notify_count.borrow(), 3);
5046// pane.read_with(cx, |pane, _| {
5047// assert!(!pane.can_navigate_backward());
5048// assert!(pane.can_navigate_forward());
5049// });
5050// }
5051
5052// #[gpui::test]
5053// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
5054// init_test(cx);
5055// let fs = FakeFs::new(cx.background());
5056
5057// let project = Project::test(fs, [], cx).await;
5058// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5059// let workspace = window.root(cx);
5060
5061// let panel = workspace.update(cx, |workspace, cx| {
5062// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5063// workspace.add_panel(panel.clone(), cx);
5064
5065// workspace
5066// .right_dock()
5067// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5068
5069// panel
5070// });
5071
5072// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5073// pane.update(cx, |pane, cx| {
5074// let item = cx.build_view(|_| TestItem::new());
5075// pane.add_item(Box::new(item), true, true, None, cx);
5076// });
5077
5078// // Transfer focus from center to panel
5079// workspace.update(cx, |workspace, cx| {
5080// workspace.toggle_panel_focus::<TestPanel>(cx);
5081// });
5082
5083// workspace.read_with(cx, |workspace, cx| {
5084// assert!(workspace.right_dock().read(cx).is_open());
5085// assert!(!panel.is_zoomed(cx));
5086// assert!(panel.has_focus(cx));
5087// });
5088
5089// // Transfer focus from panel to center
5090// workspace.update(cx, |workspace, cx| {
5091// workspace.toggle_panel_focus::<TestPanel>(cx);
5092// });
5093
5094// workspace.read_with(cx, |workspace, cx| {
5095// assert!(workspace.right_dock().read(cx).is_open());
5096// assert!(!panel.is_zoomed(cx));
5097// assert!(!panel.has_focus(cx));
5098// });
5099
5100// // Close the dock
5101// workspace.update(cx, |workspace, cx| {
5102// workspace.toggle_dock(DockPosition::Right, cx);
5103// });
5104
5105// workspace.read_with(cx, |workspace, cx| {
5106// assert!(!workspace.right_dock().read(cx).is_open());
5107// assert!(!panel.is_zoomed(cx));
5108// assert!(!panel.has_focus(cx));
5109// });
5110
5111// // Open the dock
5112// workspace.update(cx, |workspace, cx| {
5113// workspace.toggle_dock(DockPosition::Right, cx);
5114// });
5115
5116// workspace.read_with(cx, |workspace, cx| {
5117// assert!(workspace.right_dock().read(cx).is_open());
5118// assert!(!panel.is_zoomed(cx));
5119// assert!(panel.has_focus(cx));
5120// });
5121
5122// // Focus and zoom panel
5123// panel.update(cx, |panel, cx| {
5124// cx.focus_self();
5125// panel.set_zoomed(true, cx)
5126// });
5127
5128// workspace.read_with(cx, |workspace, cx| {
5129// assert!(workspace.right_dock().read(cx).is_open());
5130// assert!(panel.is_zoomed(cx));
5131// assert!(panel.has_focus(cx));
5132// });
5133
5134// // Transfer focus to the center closes the dock
5135// workspace.update(cx, |workspace, cx| {
5136// workspace.toggle_panel_focus::<TestPanel>(cx);
5137// });
5138
5139// workspace.read_with(cx, |workspace, cx| {
5140// assert!(!workspace.right_dock().read(cx).is_open());
5141// assert!(panel.is_zoomed(cx));
5142// assert!(!panel.has_focus(cx));
5143// });
5144
5145// // Transferring focus back to the panel keeps it zoomed
5146// workspace.update(cx, |workspace, cx| {
5147// workspace.toggle_panel_focus::<TestPanel>(cx);
5148// });
5149
5150// workspace.read_with(cx, |workspace, cx| {
5151// assert!(workspace.right_dock().read(cx).is_open());
5152// assert!(panel.is_zoomed(cx));
5153// assert!(panel.has_focus(cx));
5154// });
5155
5156// // Close the dock while it is zoomed
5157// workspace.update(cx, |workspace, cx| {
5158// workspace.toggle_dock(DockPosition::Right, cx)
5159// });
5160
5161// workspace.read_with(cx, |workspace, cx| {
5162// assert!(!workspace.right_dock().read(cx).is_open());
5163// assert!(panel.is_zoomed(cx));
5164// assert!(workspace.zoomed.is_none());
5165// assert!(!panel.has_focus(cx));
5166// });
5167
5168// // Opening the dock, when it's zoomed, retains focus
5169// workspace.update(cx, |workspace, cx| {
5170// workspace.toggle_dock(DockPosition::Right, cx)
5171// });
5172
5173// workspace.read_with(cx, |workspace, cx| {
5174// assert!(workspace.right_dock().read(cx).is_open());
5175// assert!(panel.is_zoomed(cx));
5176// assert!(workspace.zoomed.is_some());
5177// assert!(panel.has_focus(cx));
5178// });
5179
5180// // Unzoom and close the panel, zoom the active pane.
5181// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
5182// workspace.update(cx, |workspace, cx| {
5183// workspace.toggle_dock(DockPosition::Right, cx)
5184// });
5185// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
5186
5187// // Opening a dock unzooms the pane.
5188// workspace.update(cx, |workspace, cx| {
5189// workspace.toggle_dock(DockPosition::Right, cx)
5190// });
5191// workspace.read_with(cx, |workspace, cx| {
5192// let pane = pane.read(cx);
5193// assert!(!pane.is_zoomed());
5194// assert!(!pane.has_focus());
5195// assert!(workspace.right_dock().read(cx).is_open());
5196// assert!(workspace.zoomed.is_none());
5197// });
5198// }
5199
5200// #[gpui::test]
5201// async fn test_panels(cx: &mut gpui::TestAppContext) {
5202// init_test(cx);
5203// let fs = FakeFs::new(cx.background());
5204
5205// let project = Project::test(fs, [], cx).await;
5206// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
5207// let workspace = window.root(cx);
5208
5209// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
5210// // Add panel_1 on the left, panel_2 on the right.
5211// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
5212// workspace.add_panel(panel_1.clone(), cx);
5213// workspace
5214// .left_dock()
5215// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
5216// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
5217// workspace.add_panel(panel_2.clone(), cx);
5218// workspace
5219// .right_dock()
5220// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
5221
5222// let left_dock = workspace.left_dock();
5223// assert_eq!(
5224// left_dock.read(cx).visible_panel().unwrap().id(),
5225// panel_1.id()
5226// );
5227// assert_eq!(
5228// left_dock.read(cx).active_panel_size(cx).unwrap(),
5229// panel_1.size(cx)
5230// );
5231
5232// left_dock.update(cx, |left_dock, cx| {
5233// left_dock.resize_active_panel(Some(1337.), cx)
5234// });
5235// assert_eq!(
5236// workspace
5237// .right_dock()
5238// .read(cx)
5239// .visible_panel()
5240// .unwrap()
5241// .id(),
5242// panel_2.id()
5243// );
5244
5245// (panel_1, panel_2)
5246// });
5247
5248// // Move panel_1 to the right
5249// panel_1.update(cx, |panel_1, cx| {
5250// panel_1.set_position(DockPosition::Right, cx)
5251// });
5252
5253// workspace.update(cx, |workspace, cx| {
5254// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
5255// // Since it was the only panel on the left, the left dock should now be closed.
5256// assert!(!workspace.left_dock().read(cx).is_open());
5257// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
5258// let right_dock = workspace.right_dock();
5259// assert_eq!(
5260// right_dock.read(cx).visible_panel().unwrap().id(),
5261// panel_1.id()
5262// );
5263// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5264
5265// // Now we move panel_2Β to the left
5266// panel_2.set_position(DockPosition::Left, cx);
5267// });
5268
5269// workspace.update(cx, |workspace, cx| {
5270// // Since panel_2 was not visible on the right, we don't open the left dock.
5271// assert!(!workspace.left_dock().read(cx).is_open());
5272// // And the right dock is unaffected in it's displaying of panel_1
5273// assert!(workspace.right_dock().read(cx).is_open());
5274// assert_eq!(
5275// workspace
5276// .right_dock()
5277// .read(cx)
5278// .visible_panel()
5279// .unwrap()
5280// .id(),
5281// panel_1.id()
5282// );
5283// });
5284
5285// // Move panel_1 back to the left
5286// panel_1.update(cx, |panel_1, cx| {
5287// panel_1.set_position(DockPosition::Left, cx)
5288// });
5289
5290// workspace.update(cx, |workspace, cx| {
5291// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
5292// let left_dock = workspace.left_dock();
5293// assert!(left_dock.read(cx).is_open());
5294// assert_eq!(
5295// left_dock.read(cx).visible_panel().unwrap().id(),
5296// panel_1.id()
5297// );
5298// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
5299// // And right the dock should be closed as it no longer has any panels.
5300// assert!(!workspace.right_dock().read(cx).is_open());
5301
5302// // Now we move panel_1 to the bottom
5303// panel_1.set_position(DockPosition::Bottom, cx);
5304// });
5305
5306// workspace.update(cx, |workspace, cx| {
5307// // Since panel_1 was visible on the left, we close the left dock.
5308// assert!(!workspace.left_dock().read(cx).is_open());
5309// // The bottom dock is sized based on the panel's default size,
5310// // since the panel orientation changed from vertical to horizontal.
5311// let bottom_dock = workspace.bottom_dock();
5312// assert_eq!(
5313// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
5314// panel_1.size(cx),
5315// );
5316// // Close bottom dock and move panel_1 back to the left.
5317// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
5318// panel_1.set_position(DockPosition::Left, cx);
5319// });
5320
5321// // Emit activated event on panel 1
5322// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
5323
5324// // Now the left dock is open and panel_1 is active and focused.
5325// workspace.read_with(cx, |workspace, cx| {
5326// let left_dock = workspace.left_dock();
5327// assert!(left_dock.read(cx).is_open());
5328// assert_eq!(
5329// left_dock.read(cx).visible_panel().unwrap().id(),
5330// panel_1.id()
5331// );
5332// assert!(panel_1.is_focused(cx));
5333// });
5334
5335// // Emit closed event on panel 2, which is not active
5336// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5337
5338// // Wo don't close the left dock, because panel_2 wasn't the active panel
5339// workspace.read_with(cx, |workspace, cx| {
5340// let left_dock = workspace.left_dock();
5341// assert!(left_dock.read(cx).is_open());
5342// assert_eq!(
5343// left_dock.read(cx).visible_panel().unwrap().id(),
5344// panel_1.id()
5345// );
5346// });
5347
5348// // Emitting a ZoomIn event shows the panel as zoomed.
5349// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
5350// workspace.read_with(cx, |workspace, _| {
5351// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5352// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
5353// });
5354
5355// // Move panel to another dock while it is zoomed
5356// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
5357// workspace.read_with(cx, |workspace, _| {
5358// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5359// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5360// });
5361
5362// // If focus is transferred to another view that's not a panel or another pane, we still show
5363// // the panel as zoomed.
5364// let focus_receiver = window.build_view(cx, |_| EmptyView);
5365// focus_receiver.update(cx, |_, cx| cx.focus_self());
5366// workspace.read_with(cx, |workspace, _| {
5367// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5368// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5369// });
5370
5371// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
5372// workspace.update(cx, |_, cx| cx.focus_self());
5373// workspace.read_with(cx, |workspace, _| {
5374// assert_eq!(workspace.zoomed, None);
5375// assert_eq!(workspace.zoomed_position, None);
5376// });
5377
5378// // If focus is transferred again to another view that's not a panel or a pane, we won't
5379// // show the panel as zoomed because it wasn't zoomed before.
5380// focus_receiver.update(cx, |_, cx| cx.focus_self());
5381// workspace.read_with(cx, |workspace, _| {
5382// assert_eq!(workspace.zoomed, None);
5383// assert_eq!(workspace.zoomed_position, None);
5384// });
5385
5386// // When focus is transferred back to the panel, it is zoomed again.
5387// panel_1.update(cx, |_, cx| cx.focus_self());
5388// workspace.read_with(cx, |workspace, _| {
5389// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
5390// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
5391// });
5392
5393// // Emitting a ZoomOut event unzooms the panel.
5394// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
5395// workspace.read_with(cx, |workspace, _| {
5396// assert_eq!(workspace.zoomed, None);
5397// assert_eq!(workspace.zoomed_position, None);
5398// });
5399
5400// // Emit closed event on panel 1, which is active
5401// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
5402
5403// // Now the left dock is closed, because panel_1 was the active panel
5404// workspace.read_with(cx, |workspace, cx| {
5405// let right_dock = workspace.right_dock();
5406// assert!(!right_dock.read(cx).is_open());
5407// });
5408// }
5409
5410// pub fn init_test(cx: &mut TestAppContext) {
5411// cx.foreground().forbid_parking();
5412// cx.update(|cx| {
5413// cx.set_global(SettingsStore::test(cx));
5414// theme::init((), cx);
5415// language::init(cx);
5416// crate::init_settings(cx);
5417// Project::init_settings(cx);
5418// });
5419// }
5420// }