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