1// mod dragged_item_receiver;
2
3use crate::{
4 item::{Item, ItemHandle, WeakItemHandle},
5 toolbar::Toolbar,
6 SplitDirection, Workspace,
7};
8use anyhow::Result;
9use collections::{HashMap, VecDeque};
10use gpui2::{
11 AppContext, EventEmitter, Model, Task, View, ViewContext, VisualContext, WeakView,
12 WindowContext,
13};
14use parking_lot::Mutex;
15use project2::{Project, ProjectEntryId, ProjectPath};
16use serde::Deserialize;
17use std::{
18 any::Any,
19 cmp, fmt, mem,
20 path::PathBuf,
21 sync::{
22 atomic::{AtomicUsize, Ordering},
23 Arc,
24 },
25};
26
27#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
28#[serde(rename_all = "camelCase")]
29pub enum SaveIntent {
30 /// write all files (even if unchanged)
31 /// prompt before overwriting on-disk changes
32 Save,
33 /// write any files that have local changes
34 /// prompt before overwriting on-disk changes
35 SaveAll,
36 /// always prompt for a new path
37 SaveAs,
38 /// prompt "you have unsaved changes" before writing
39 Close,
40 /// write all dirty files, don't prompt on conflict
41 Overwrite,
42 /// skip all save-related behavior
43 Skip,
44}
45
46// #[derive(Clone, Deserialize, PartialEq)]
47// pub struct ActivateItem(pub usize);
48
49// #[derive(Clone, PartialEq)]
50// pub struct CloseItemById {
51// pub item_id: usize,
52// pub pane: WeakView<Pane>,
53// }
54
55// #[derive(Clone, PartialEq)]
56// pub struct CloseItemsToTheLeftById {
57// pub item_id: usize,
58// pub pane: WeakView<Pane>,
59// }
60
61// #[derive(Clone, PartialEq)]
62// pub struct CloseItemsToTheRightById {
63// pub item_id: usize,
64// pub pane: WeakView<Pane>,
65// }
66
67// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
68// #[serde(rename_all = "camelCase")]
69// pub struct CloseActiveItem {
70// pub save_intent: Option<SaveIntent>,
71// }
72
73// #[derive(Clone, PartialEq, Debug, Deserialize)]
74// #[serde(rename_all = "camelCase")]
75// pub struct CloseAllItems {
76// pub save_intent: Option<SaveIntent>,
77// }
78
79// todo!()
80// actions!(
81// pane,
82// [
83// ActivatePrevItem,
84// ActivateNextItem,
85// ActivateLastItem,
86// CloseInactiveItems,
87// CloseCleanItems,
88// CloseItemsToTheLeft,
89// CloseItemsToTheRight,
90// GoBack,
91// GoForward,
92// ReopenClosedItem,
93// SplitLeft,
94// SplitUp,
95// SplitRight,
96// SplitDown,
97// ]
98// );
99
100// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
101
102const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
103
104// todo!()
105// pub fn init(cx: &mut AppContext) {
106// cx.add_action(Pane::toggle_zoom);
107// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
108// pane.activate_item(action.0, true, true, cx);
109// });
110// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
111// pane.activate_item(pane.items.len() - 1, true, true, cx);
112// });
113// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
114// pane.activate_prev_item(true, cx);
115// });
116// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
117// pane.activate_next_item(true, cx);
118// });
119// cx.add_async_action(Pane::close_active_item);
120// cx.add_async_action(Pane::close_inactive_items);
121// cx.add_async_action(Pane::close_clean_items);
122// cx.add_async_action(Pane::close_items_to_the_left);
123// cx.add_async_action(Pane::close_items_to_the_right);
124// cx.add_async_action(Pane::close_all_items);
125// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
126// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
127// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
128// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
129// }
130
131pub enum Event {
132 AddItem { item: Box<dyn ItemHandle> },
133 ActivateItem { local: bool },
134 Remove,
135 RemoveItem { item_id: usize },
136 Split(SplitDirection),
137 ChangeItemTitle,
138 Focus,
139 ZoomIn,
140 ZoomOut,
141}
142
143impl fmt::Debug for Event {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Event::AddItem { item } => f.debug_struct("AddItem").field("item", &item.id()).finish(),
147 Event::ActivateItem { local } => f
148 .debug_struct("ActivateItem")
149 .field("local", local)
150 .finish(),
151 Event::Remove => f.write_str("Remove"),
152 Event::RemoveItem { item_id } => f
153 .debug_struct("RemoveItem")
154 .field("item_id", item_id)
155 .finish(),
156 Event::Split(direction) => f
157 .debug_struct("Split")
158 .field("direction", direction)
159 .finish(),
160 Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
161 Event::Focus => f.write_str("Focus"),
162 Event::ZoomIn => f.write_str("ZoomIn"),
163 Event::ZoomOut => f.write_str("ZoomOut"),
164 }
165 }
166}
167
168pub struct Pane {
169 items: Vec<Box<dyn ItemHandle>>,
170 activation_history: Vec<usize>,
171 zoomed: bool,
172 active_item_index: usize,
173 // last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
174 autoscroll: bool,
175 nav_history: NavHistory,
176 toolbar: View<Toolbar>,
177 // tab_bar_context_menu: TabBarContextMenu,
178 // tab_context_menu: ViewHandle<ContextMenu>,
179 // workspace: WeakView<Workspace>,
180 project: Model<Project>,
181 has_focus: bool,
182 // can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
183 // can_split: bool,
184 // render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
185}
186
187pub struct ItemNavHistory {
188 history: NavHistory,
189 item: Arc<dyn WeakItemHandle>,
190}
191
192#[derive(Clone)]
193pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
194
195struct NavHistoryState {
196 mode: NavigationMode,
197 backward_stack: VecDeque<NavigationEntry>,
198 forward_stack: VecDeque<NavigationEntry>,
199 closed_stack: VecDeque<NavigationEntry>,
200 paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
201 pane: WeakView<Pane>,
202 next_timestamp: Arc<AtomicUsize>,
203}
204
205#[derive(Copy, Clone)]
206pub enum NavigationMode {
207 Normal,
208 GoingBack,
209 GoingForward,
210 ClosingItem,
211 ReopeningClosedItem,
212 Disabled,
213}
214
215impl Default for NavigationMode {
216 fn default() -> Self {
217 Self::Normal
218 }
219}
220
221pub struct NavigationEntry {
222 pub item: Arc<dyn WeakItemHandle>,
223 pub data: Option<Box<dyn Any + Send>>,
224 pub timestamp: usize,
225}
226
227// pub struct DraggedItem {
228// pub handle: Box<dyn ItemHandle>,
229// pub pane: WeakView<Pane>,
230// }
231
232// pub enum ReorderBehavior {
233// None,
234// MoveAfterActive,
235// MoveToIndex(usize),
236// }
237
238// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
239// enum TabBarContextMenuKind {
240// New,
241// Split,
242// }
243
244// struct TabBarContextMenu {
245// kind: TabBarContextMenuKind,
246// handle: ViewHandle<ContextMenu>,
247// }
248
249// impl TabBarContextMenu {
250// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
251// if self.kind == kind {
252// return Some(self.handle.clone());
253// }
254// None
255// }
256// }
257
258// #[allow(clippy::too_many_arguments)]
259// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
260// svg_path: &'static str,
261// style: theme2::Interactive<theme2::IconButton>,
262// nav_button_height: f32,
263// tooltip_style: TooltipStyle,
264// enabled: bool,
265// on_click: F,
266// tooltip_action: A,
267// action_name: &str,
268// cx: &mut ViewContext<Pane>,
269// ) -> AnyElement<Pane> {
270// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
271// let style = if enabled {
272// style.style_for(state)
273// } else {
274// style.disabled_style()
275// };
276// Svg::new(svg_path)
277// .with_color(style.color)
278// .constrained()
279// .with_width(style.icon_width)
280// .aligned()
281// .contained()
282// .with_style(style.container)
283// .constrained()
284// .with_width(style.button_width)
285// .with_height(nav_button_height)
286// .aligned()
287// .top()
288// })
289// .with_cursor_style(if enabled {
290// CursorStyle::PointingHand
291// } else {
292// CursorStyle::default()
293// })
294// .on_click(MouseButton::Left, move |_, toolbar, cx| {
295// on_click(toolbar, cx)
296// })
297// .with_tooltip::<A>(
298// 0,
299// action_name.to_string(),
300// Some(Box::new(tooltip_action)),
301// tooltip_style,
302// cx,
303// )
304// .contained()
305// .into_any_named("nav button")
306// }
307
308impl EventEmitter for Pane {
309 type Event = Event;
310}
311
312impl Pane {
313 pub fn new(
314 workspace: WeakView<Workspace>,
315 project: Model<Project>,
316 next_timestamp: Arc<AtomicUsize>,
317 cx: &mut ViewContext<Self>,
318 ) -> Self {
319 // todo!("context menu")
320 // let pane_view_id = cx.view_id();
321 // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
322 // context_menu.update(cx, |menu, _| {
323 // menu.set_position_mode(OverlayPositionMode::Local)
324 // });
325
326 let handle = cx.view().downgrade();
327 Self {
328 items: Vec::new(),
329 activation_history: Vec::new(),
330 zoomed: false,
331 active_item_index: 0,
332 // last_focused_view_by_item: Default::default(),
333 autoscroll: false,
334 nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
335 mode: NavigationMode::Normal,
336 backward_stack: Default::default(),
337 forward_stack: Default::default(),
338 closed_stack: Default::default(),
339 paths_by_item: Default::default(),
340 pane: handle.clone(),
341 next_timestamp,
342 }))),
343 toolbar: cx.build_view(|_| Toolbar::new()),
344 // tab_bar_context_menu: TabBarContextMenu {
345 // kind: TabBarContextMenuKind::New,
346 // handle: context_menu,
347 // },
348 // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
349 // workspace,
350 project,
351 has_focus: false,
352 // can_drop: Rc::new(|_, _| true),
353 // can_split: true,
354 // render_tab_bar_buttons: Rc::new(move |pane, cx| {
355 // Flex::row()
356 // // New menu
357 // .with_child(Self::render_tab_bar_button(
358 // 0,
359 // "icons/plus.svg",
360 // false,
361 // Some(("New...".into(), None)),
362 // cx,
363 // |pane, cx| pane.deploy_new_menu(cx),
364 // |pane, cx| {
365 // pane.tab_bar_context_menu
366 // .handle
367 // .update(cx, |menu, _| menu.delay_cancel())
368 // },
369 // pane.tab_bar_context_menu
370 // .handle_if_kind(TabBarContextMenuKind::New),
371 // ))
372 // .with_child(Self::render_tab_bar_button(
373 // 1,
374 // "icons/split.svg",
375 // false,
376 // Some(("Split Pane".into(), None)),
377 // cx,
378 // |pane, cx| pane.deploy_split_menu(cx),
379 // |pane, cx| {
380 // pane.tab_bar_context_menu
381 // .handle
382 // .update(cx, |menu, _| menu.delay_cancel())
383 // },
384 // pane.tab_bar_context_menu
385 // .handle_if_kind(TabBarContextMenuKind::Split),
386 // ))
387 // .with_child({
388 // let icon_path;
389 // let tooltip_label;
390 // if pane.is_zoomed() {
391 // icon_path = "icons/minimize.svg";
392 // tooltip_label = "Zoom In";
393 // } else {
394 // icon_path = "icons/maximize.svg";
395 // tooltip_label = "Zoom In";
396 // }
397
398 // Pane::render_tab_bar_button(
399 // 2,
400 // icon_path,
401 // pane.is_zoomed(),
402 // Some((tooltip_label, Some(Box::new(ToggleZoom)))),
403 // cx,
404 // move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
405 // move |_, _| {},
406 // None,
407 // )
408 // })
409 // .into_any()
410 // }),
411 }
412 }
413
414 // pub(crate) fn workspace(&self) -> &WeakView<Workspace> {
415 // &self.workspace
416 // }
417
418 pub fn has_focus(&self) -> bool {
419 self.has_focus
420 }
421
422 // pub fn active_item_index(&self) -> usize {
423 // self.active_item_index
424 // }
425
426 // pub fn on_can_drop<F>(&mut self, can_drop: F)
427 // where
428 // F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
429 // {
430 // self.can_drop = Rc::new(can_drop);
431 // }
432
433 // pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
434 // self.can_split = can_split;
435 // cx.notify();
436 // }
437
438 // pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
439 // self.toolbar.update(cx, |toolbar, cx| {
440 // toolbar.set_can_navigate(can_navigate, cx);
441 // });
442 // cx.notify();
443 // }
444
445 // pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
446 // where
447 // F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
448 // {
449 // self.render_tab_bar_buttons = Rc::new(render);
450 // cx.notify();
451 // }
452
453 pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
454 ItemNavHistory {
455 history: self.nav_history.clone(),
456 item: Arc::new(item.downgrade()),
457 }
458 }
459
460 pub fn nav_history(&self) -> &NavHistory {
461 &self.nav_history
462 }
463
464 pub fn nav_history_mut(&mut self) -> &mut NavHistory {
465 &mut self.nav_history
466 }
467
468 pub fn disable_history(&mut self) {
469 self.nav_history.disable();
470 }
471
472 pub fn enable_history(&mut self) {
473 self.nav_history.enable();
474 }
475
476 pub fn can_navigate_backward(&self) -> bool {
477 !self.nav_history.0.lock().backward_stack.is_empty()
478 }
479
480 pub fn can_navigate_forward(&self) -> bool {
481 !self.nav_history.0.lock().forward_stack.is_empty()
482 }
483
484 fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
485 self.toolbar.update(cx, |_, cx| cx.notify());
486 }
487
488 pub(crate) fn open_item(
489 &mut self,
490 project_entry_id: ProjectEntryId,
491 focus_item: bool,
492 cx: &mut ViewContext<Self>,
493 build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
494 ) -> Box<dyn ItemHandle> {
495 let mut existing_item = None;
496 for (index, item) in self.items.iter().enumerate() {
497 if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [project_entry_id]
498 {
499 let item = item.boxed_clone();
500 existing_item = Some((index, item));
501 break;
502 }
503 }
504
505 if let Some((index, existing_item)) = existing_item {
506 self.activate_item(index, focus_item, focus_item, cx);
507 existing_item
508 } else {
509 let new_item = build_item(cx);
510 self.add_item(new_item.clone(), true, focus_item, None, cx);
511 new_item
512 }
513 }
514
515 pub fn add_item(
516 &mut self,
517 item: Box<dyn ItemHandle>,
518 activate_pane: bool,
519 focus_item: bool,
520 destination_index: Option<usize>,
521 cx: &mut ViewContext<Self>,
522 ) {
523 if item.is_singleton(cx) {
524 if let Some(&entry_id) = item.project_entry_ids(cx).get(0) {
525 let project = self.project.read(cx);
526 if let Some(project_path) = project.path_for_entry(entry_id, cx) {
527 let abs_path = project.absolute_path(&project_path, cx);
528 self.nav_history
529 .0
530 .lock()
531 .paths_by_item
532 .insert(item.id(), (project_path, abs_path));
533 }
534 }
535 }
536 // If no destination index is specified, add or move the item after the active item.
537 let mut insertion_index = {
538 cmp::min(
539 if let Some(destination_index) = destination_index {
540 destination_index
541 } else {
542 self.active_item_index + 1
543 },
544 self.items.len(),
545 )
546 };
547
548 // Does the item already exist?
549 let project_entry_id = if item.is_singleton(cx) {
550 item.project_entry_ids(cx).get(0).copied()
551 } else {
552 None
553 };
554
555 let existing_item_index = self.items.iter().position(|existing_item| {
556 if existing_item.id() == item.id() {
557 true
558 } else if existing_item.is_singleton(cx) {
559 existing_item
560 .project_entry_ids(cx)
561 .get(0)
562 .map_or(false, |existing_entry_id| {
563 Some(existing_entry_id) == project_entry_id.as_ref()
564 })
565 } else {
566 false
567 }
568 });
569
570 if let Some(existing_item_index) = existing_item_index {
571 // If the item already exists, move it to the desired destination and activate it
572
573 if existing_item_index != insertion_index {
574 let existing_item_is_active = existing_item_index == self.active_item_index;
575
576 // If the caller didn't specify a destination and the added item is already
577 // the active one, don't move it
578 if existing_item_is_active && destination_index.is_none() {
579 insertion_index = existing_item_index;
580 } else {
581 self.items.remove(existing_item_index);
582 if existing_item_index < self.active_item_index {
583 self.active_item_index -= 1;
584 }
585 insertion_index = insertion_index.min(self.items.len());
586
587 self.items.insert(insertion_index, item.clone());
588
589 if existing_item_is_active {
590 self.active_item_index = insertion_index;
591 } else if insertion_index <= self.active_item_index {
592 self.active_item_index += 1;
593 }
594 }
595
596 cx.notify();
597 }
598
599 self.activate_item(insertion_index, activate_pane, focus_item, cx);
600 } else {
601 self.items.insert(insertion_index, item.clone());
602 if insertion_index <= self.active_item_index {
603 self.active_item_index += 1;
604 }
605
606 self.activate_item(insertion_index, activate_pane, focus_item, cx);
607 cx.notify();
608 }
609
610 cx.emit(Event::AddItem { item });
611 }
612
613 pub fn items_len(&self) -> usize {
614 self.items.len()
615 }
616
617 pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
618 self.items.iter()
619 }
620
621 // pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
622 // self.items
623 // .iter()
624 // .filter_map(|item| item.as_any().clone().downcast())
625 // }
626
627 pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
628 self.items.get(self.active_item_index).cloned()
629 }
630
631 // pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
632 // self.items
633 // .get(self.active_item_index)?
634 // .pixel_position_of_cursor(cx)
635 // }
636
637 // pub fn item_for_entry(
638 // &self,
639 // entry_id: ProjectEntryId,
640 // cx: &AppContext,
641 // ) -> Option<Box<dyn ItemHandle>> {
642 // self.items.iter().find_map(|item| {
643 // if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
644 // Some(item.boxed_clone())
645 // } else {
646 // None
647 // }
648 // })
649 // }
650
651 pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
652 self.items.iter().position(|i| i.id() == item.id())
653 }
654
655 // pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
656 // // Potentially warn the user of the new keybinding
657 // let workspace_handle = self.workspace().clone();
658 // cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
659 // .detach();
660
661 // if self.zoomed {
662 // cx.emit(Event::ZoomOut);
663 // } else if !self.items.is_empty() {
664 // if !self.has_focus {
665 // cx.focus_self();
666 // }
667 // cx.emit(Event::ZoomIn);
668 // }
669 // }
670
671 pub fn activate_item(
672 &mut self,
673 index: usize,
674 activate_pane: bool,
675 focus_item: bool,
676 cx: &mut ViewContext<Self>,
677 ) {
678 use NavigationMode::{GoingBack, GoingForward};
679
680 if index < self.items.len() {
681 let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
682 if prev_active_item_ix != self.active_item_index
683 || matches!(self.nav_history.mode(), GoingBack | GoingForward)
684 {
685 if let Some(prev_item) = self.items.get(prev_active_item_ix) {
686 prev_item.deactivated(cx);
687 }
688
689 cx.emit(Event::ActivateItem {
690 local: activate_pane,
691 });
692 }
693
694 if let Some(newly_active_item) = self.items.get(index) {
695 self.activation_history
696 .retain(|&previously_active_item_id| {
697 previously_active_item_id != newly_active_item.id()
698 });
699 self.activation_history.push(newly_active_item.id());
700 }
701
702 self.update_toolbar(cx);
703
704 if focus_item {
705 self.focus_active_item(cx);
706 }
707
708 self.autoscroll = true;
709 cx.notify();
710 }
711 }
712
713 // pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
714 // let mut index = self.active_item_index;
715 // if index > 0 {
716 // index -= 1;
717 // } else if !self.items.is_empty() {
718 // index = self.items.len() - 1;
719 // }
720 // self.activate_item(index, activate_pane, activate_pane, cx);
721 // }
722
723 // pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
724 // let mut index = self.active_item_index;
725 // if index + 1 < self.items.len() {
726 // index += 1;
727 // } else {
728 // index = 0;
729 // }
730 // self.activate_item(index, activate_pane, activate_pane, cx);
731 // }
732
733 // pub fn close_active_item(
734 // &mut self,
735 // action: &CloseActiveItem,
736 // cx: &mut ViewContext<Self>,
737 // ) -> Option<Task<Result<()>>> {
738 // if self.items.is_empty() {
739 // return None;
740 // }
741 // let active_item_id = self.items[self.active_item_index].id();
742 // Some(self.close_item_by_id(
743 // active_item_id,
744 // action.save_intent.unwrap_or(SaveIntent::Close),
745 // cx,
746 // ))
747 // }
748
749 pub fn close_item_by_id(
750 &mut self,
751 item_id_to_close: usize,
752 save_intent: SaveIntent,
753 cx: &mut ViewContext<Self>,
754 ) -> Task<Result<()>> {
755 // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
756 todo!()
757 }
758
759 // pub fn close_inactive_items(
760 // &mut self,
761 // _: &CloseInactiveItems,
762 // cx: &mut ViewContext<Self>,
763 // ) -> Option<Task<Result<()>>> {
764 // if self.items.is_empty() {
765 // return None;
766 // }
767
768 // let active_item_id = self.items[self.active_item_index].id();
769 // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
770 // item_id != active_item_id
771 // }))
772 // }
773
774 // pub fn close_clean_items(
775 // &mut self,
776 // _: &CloseCleanItems,
777 // cx: &mut ViewContext<Self>,
778 // ) -> Option<Task<Result<()>>> {
779 // let item_ids: Vec<_> = self
780 // .items()
781 // .filter(|item| !item.is_dirty(cx))
782 // .map(|item| item.id())
783 // .collect();
784 // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
785 // item_ids.contains(&item_id)
786 // }))
787 // }
788
789 // pub fn close_items_to_the_left(
790 // &mut self,
791 // _: &CloseItemsToTheLeft,
792 // cx: &mut ViewContext<Self>,
793 // ) -> Option<Task<Result<()>>> {
794 // if self.items.is_empty() {
795 // return None;
796 // }
797 // let active_item_id = self.items[self.active_item_index].id();
798 // Some(self.close_items_to_the_left_by_id(active_item_id, cx))
799 // }
800
801 // pub fn close_items_to_the_left_by_id(
802 // &mut self,
803 // item_id: usize,
804 // cx: &mut ViewContext<Self>,
805 // ) -> Task<Result<()>> {
806 // let item_ids: Vec<_> = self
807 // .items()
808 // .take_while(|item| item.id() != item_id)
809 // .map(|item| item.id())
810 // .collect();
811 // self.close_items(cx, SaveIntent::Close, move |item_id| {
812 // item_ids.contains(&item_id)
813 // })
814 // }
815
816 // pub fn close_items_to_the_right(
817 // &mut self,
818 // _: &CloseItemsToTheRight,
819 // cx: &mut ViewContext<Self>,
820 // ) -> Option<Task<Result<()>>> {
821 // if self.items.is_empty() {
822 // return None;
823 // }
824 // let active_item_id = self.items[self.active_item_index].id();
825 // Some(self.close_items_to_the_right_by_id(active_item_id, cx))
826 // }
827
828 // pub fn close_items_to_the_right_by_id(
829 // &mut self,
830 // item_id: usize,
831 // cx: &mut ViewContext<Self>,
832 // ) -> Task<Result<()>> {
833 // let item_ids: Vec<_> = self
834 // .items()
835 // .rev()
836 // .take_while(|item| item.id() != item_id)
837 // .map(|item| item.id())
838 // .collect();
839 // self.close_items(cx, SaveIntent::Close, move |item_id| {
840 // item_ids.contains(&item_id)
841 // })
842 // }
843
844 // pub fn close_all_items(
845 // &mut self,
846 // action: &CloseAllItems,
847 // cx: &mut ViewContext<Self>,
848 // ) -> Option<Task<Result<()>>> {
849 // if self.items.is_empty() {
850 // return None;
851 // }
852
853 // Some(
854 // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
855 // true
856 // }),
857 // )
858 // }
859
860 // pub(super) fn file_names_for_prompt(
861 // items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
862 // all_dirty_items: usize,
863 // cx: &AppContext,
864 // ) -> String {
865 // /// Quantity of item paths displayed in prompt prior to cutoff..
866 // const FILE_NAMES_CUTOFF_POINT: usize = 10;
867 // let mut file_names: Vec<_> = items
868 // .filter_map(|item| {
869 // item.project_path(cx).and_then(|project_path| {
870 // project_path
871 // .path
872 // .file_name()
873 // .and_then(|name| name.to_str().map(ToOwned::to_owned))
874 // })
875 // })
876 // .take(FILE_NAMES_CUTOFF_POINT)
877 // .collect();
878 // let should_display_followup_text =
879 // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
880 // if should_display_followup_text {
881 // let not_shown_files = all_dirty_items - file_names.len();
882 // if not_shown_files == 1 {
883 // file_names.push(".. 1 file not shown".into());
884 // } else {
885 // file_names.push(format!(".. {} files not shown", not_shown_files).into());
886 // }
887 // }
888 // let file_names = file_names.join("\n");
889 // format!(
890 // "Do you want to save changes to the following {} files?\n{file_names}",
891 // all_dirty_items
892 // )
893 // }
894
895 // pub fn close_items(
896 // &mut self,
897 // cx: &mut ViewContext<Pane>,
898 // mut save_intent: SaveIntent,
899 // should_close: impl 'static + Fn(usize) -> bool,
900 // ) -> Task<Result<()>> {
901 // // Find the items to close.
902 // let mut items_to_close = Vec::new();
903 // let mut dirty_items = Vec::new();
904 // for item in &self.items {
905 // if should_close(item.id()) {
906 // items_to_close.push(item.boxed_clone());
907 // if item.is_dirty(cx) {
908 // dirty_items.push(item.boxed_clone());
909 // }
910 // }
911 // }
912
913 // // If a buffer is open both in a singleton editor and in a multibuffer, make sure
914 // // to focus the singleton buffer when prompting to save that buffer, as opposed
915 // // to focusing the multibuffer, because this gives the user a more clear idea
916 // // of what content they would be saving.
917 // items_to_close.sort_by_key(|item| !item.is_singleton(cx));
918
919 // let workspace = self.workspace.clone();
920 // cx.spawn(|pane, mut cx| async move {
921 // if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
922 // let mut answer = pane.update(&mut cx, |_, cx| {
923 // let prompt =
924 // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
925 // cx.prompt(
926 // PromptLevel::Warning,
927 // &prompt,
928 // &["Save all", "Discard all", "Cancel"],
929 // )
930 // })?;
931 // match answer.next().await {
932 // Some(0) => save_intent = SaveIntent::SaveAll,
933 // Some(1) => save_intent = SaveIntent::Skip,
934 // _ => {}
935 // }
936 // }
937 // let mut saved_project_items_ids = HashSet::default();
938 // for item in items_to_close.clone() {
939 // // Find the item's current index and its set of project item models. Avoid
940 // // storing these in advance, in case they have changed since this task
941 // // was started.
942 // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
943 // (pane.index_for_item(&*item), item.project_item_model_ids(cx))
944 // })?;
945 // let item_ix = if let Some(ix) = item_ix {
946 // ix
947 // } else {
948 // continue;
949 // };
950
951 // // Check if this view has any project items that are not open anywhere else
952 // // in the workspace, AND that the user has not already been prompted to save.
953 // // If there are any such project entries, prompt the user to save this item.
954 // let project = workspace.read_with(&cx, |workspace, cx| {
955 // for item in workspace.items(cx) {
956 // if !items_to_close
957 // .iter()
958 // .any(|item_to_close| item_to_close.id() == item.id())
959 // {
960 // let other_project_item_ids = item.project_item_model_ids(cx);
961 // project_item_ids.retain(|id| !other_project_item_ids.contains(id));
962 // }
963 // }
964 // workspace.project().clone()
965 // })?;
966 // let should_save = project_item_ids
967 // .iter()
968 // .any(|id| saved_project_items_ids.insert(*id));
969
970 // if should_save
971 // && !Self::save_item(
972 // project.clone(),
973 // &pane,
974 // item_ix,
975 // &*item,
976 // save_intent,
977 // &mut cx,
978 // )
979 // .await?
980 // {
981 // break;
982 // }
983
984 // // Remove the item from the pane.
985 // pane.update(&mut cx, |pane, cx| {
986 // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
987 // pane.remove_item(item_ix, false, cx);
988 // }
989 // })?;
990 // }
991
992 // pane.update(&mut cx, |_, cx| cx.notify())?;
993 // Ok(())
994 // })
995 // }
996
997 pub fn remove_item(
998 &mut self,
999 item_index: usize,
1000 activate_pane: bool,
1001 cx: &mut ViewContext<Self>,
1002 ) {
1003 self.activation_history
1004 .retain(|&history_entry| history_entry != self.items[item_index].id());
1005
1006 if item_index == self.active_item_index {
1007 let index_to_activate = self
1008 .activation_history
1009 .pop()
1010 .and_then(|last_activated_item| {
1011 self.items.iter().enumerate().find_map(|(index, item)| {
1012 (item.id() == last_activated_item).then_some(index)
1013 })
1014 })
1015 // We didn't have a valid activation history entry, so fallback
1016 // to activating the item to the left
1017 .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
1018
1019 let should_activate = activate_pane || self.has_focus;
1020 self.activate_item(index_to_activate, should_activate, should_activate, cx);
1021 }
1022
1023 let item = self.items.remove(item_index);
1024
1025 cx.emit(Event::RemoveItem { item_id: item.id() });
1026 if self.items.is_empty() {
1027 item.deactivated(cx);
1028 self.update_toolbar(cx);
1029 cx.emit(Event::Remove);
1030 }
1031
1032 if item_index < self.active_item_index {
1033 self.active_item_index -= 1;
1034 }
1035
1036 self.nav_history.set_mode(NavigationMode::ClosingItem);
1037 item.deactivated(cx);
1038 self.nav_history.set_mode(NavigationMode::Normal);
1039
1040 if let Some(path) = item.project_path(cx) {
1041 let abs_path = self
1042 .nav_history
1043 .0
1044 .lock()
1045 .paths_by_item
1046 .get(&item.id())
1047 .and_then(|(_, abs_path)| abs_path.clone());
1048
1049 self.nav_history
1050 .0
1051 .lock()
1052 .paths_by_item
1053 .insert(item.id(), (path, abs_path));
1054 } else {
1055 self.nav_history.0.lock().paths_by_item.remove(&item.id());
1056 }
1057
1058 if self.items.is_empty() && self.zoomed {
1059 cx.emit(Event::ZoomOut);
1060 }
1061
1062 cx.notify();
1063 }
1064
1065 // pub async fn save_item(
1066 // project: Model<Project>,
1067 // pane: &WeakView<Pane>,
1068 // item_ix: usize,
1069 // item: &dyn ItemHandle,
1070 // save_intent: SaveIntent,
1071 // cx: &mut AsyncAppContext,
1072 // ) -> Result<bool> {
1073 // const CONFLICT_MESSAGE: &str =
1074 // "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1075
1076 // if save_intent == SaveIntent::Skip {
1077 // return Ok(true);
1078 // }
1079
1080 // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
1081 // (
1082 // item.has_conflict(cx),
1083 // item.is_dirty(cx),
1084 // item.can_save(cx),
1085 // item.is_singleton(cx),
1086 // )
1087 // });
1088
1089 // // when saving a single buffer, we ignore whether or not it's dirty.
1090 // if save_intent == SaveIntent::Save {
1091 // is_dirty = true;
1092 // }
1093
1094 // if save_intent == SaveIntent::SaveAs {
1095 // is_dirty = true;
1096 // has_conflict = false;
1097 // can_save = false;
1098 // }
1099
1100 // if save_intent == SaveIntent::Overwrite {
1101 // has_conflict = false;
1102 // }
1103
1104 // if has_conflict && can_save {
1105 // let mut answer = pane.update(cx, |pane, cx| {
1106 // pane.activate_item(item_ix, true, true, cx);
1107 // cx.prompt(
1108 // PromptLevel::Warning,
1109 // CONFLICT_MESSAGE,
1110 // &["Overwrite", "Discard", "Cancel"],
1111 // )
1112 // })?;
1113 // match answer.next().await {
1114 // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
1115 // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
1116 // _ => return Ok(false),
1117 // }
1118 // } else if is_dirty && (can_save || can_save_as) {
1119 // if save_intent == SaveIntent::Close {
1120 // let will_autosave = cx.read(|cx| {
1121 // matches!(
1122 // settings::get::<WorkspaceSettings>(cx).autosave,
1123 // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
1124 // ) && Self::can_autosave_item(&*item, cx)
1125 // });
1126 // if !will_autosave {
1127 // let mut answer = pane.update(cx, |pane, cx| {
1128 // pane.activate_item(item_ix, true, true, cx);
1129 // let prompt = dirty_message_for(item.project_path(cx));
1130 // cx.prompt(
1131 // PromptLevel::Warning,
1132 // &prompt,
1133 // &["Save", "Don't Save", "Cancel"],
1134 // )
1135 // })?;
1136 // match answer.next().await {
1137 // Some(0) => {}
1138 // Some(1) => return Ok(true), // Don't save his file
1139 // _ => return Ok(false), // Cancel
1140 // }
1141 // }
1142 // }
1143
1144 // if can_save {
1145 // pane.update(cx, |_, cx| item.save(project, cx))?.await?;
1146 // } else if can_save_as {
1147 // let start_abs_path = project
1148 // .read_with(cx, |project, cx| {
1149 // let worktree = project.visible_worktrees(cx).next()?;
1150 // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
1151 // })
1152 // .unwrap_or_else(|| Path::new("").into());
1153
1154 // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
1155 // if let Some(abs_path) = abs_path.next().await.flatten() {
1156 // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
1157 // .await?;
1158 // } else {
1159 // return Ok(false);
1160 // }
1161 // }
1162 // }
1163 // Ok(true)
1164 // }
1165
1166 fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
1167 let is_deleted = item.project_entry_ids(cx).is_empty();
1168 item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
1169 }
1170
1171 pub fn autosave_item(
1172 item: &dyn ItemHandle,
1173 project: Model<Project>,
1174 cx: &mut WindowContext,
1175 ) -> Task<Result<()>> {
1176 if Self::can_autosave_item(item, cx) {
1177 item.save(project, cx)
1178 } else {
1179 Task::ready(Ok(()))
1180 }
1181 }
1182
1183 pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
1184 todo!();
1185 // if let Some(active_item) = self.active_item() {
1186 // cx.focus(active_item.as_any());
1187 // }
1188 }
1189
1190 // pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
1191 // cx.emit(Event::Split(direction));
1192 // }
1193
1194 // fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
1195 // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1196 // menu.toggle(
1197 // Default::default(),
1198 // AnchorCorner::TopRight,
1199 // vec![
1200 // ContextMenuItem::action("Split Right", SplitRight),
1201 // ContextMenuItem::action("Split Left", SplitLeft),
1202 // ContextMenuItem::action("Split Up", SplitUp),
1203 // ContextMenuItem::action("Split Down", SplitDown),
1204 // ],
1205 // cx,
1206 // );
1207 // });
1208
1209 // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
1210 // }
1211
1212 // fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
1213 // self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
1214 // menu.toggle(
1215 // Default::default(),
1216 // AnchorCorner::TopRight,
1217 // vec![
1218 // ContextMenuItem::action("New File", NewFile),
1219 // ContextMenuItem::action("New Terminal", NewCenterTerminal),
1220 // ContextMenuItem::action("New Search", NewSearch),
1221 // ],
1222 // cx,
1223 // );
1224 // });
1225
1226 // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
1227 // }
1228
1229 // fn deploy_tab_context_menu(
1230 // &mut self,
1231 // position: Vector2F,
1232 // target_item_id: usize,
1233 // cx: &mut ViewContext<Self>,
1234 // ) {
1235 // let active_item_id = self.items[self.active_item_index].id();
1236 // let is_active_item = target_item_id == active_item_id;
1237 // let target_pane = cx.weak_handle();
1238
1239 // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
1240
1241 // self.tab_context_menu.update(cx, |menu, cx| {
1242 // menu.show(
1243 // position,
1244 // AnchorCorner::TopLeft,
1245 // if is_active_item {
1246 // vec![
1247 // ContextMenuItem::action(
1248 // "Close Active Item",
1249 // CloseActiveItem { save_intent: None },
1250 // ),
1251 // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1252 // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1253 // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
1254 // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
1255 // ContextMenuItem::action(
1256 // "Close All Items",
1257 // CloseAllItems { save_intent: None },
1258 // ),
1259 // ]
1260 // } else {
1261 // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
1262 // vec![
1263 // ContextMenuItem::handler("Close Inactive Item", {
1264 // let pane = target_pane.clone();
1265 // move |cx| {
1266 // if let Some(pane) = pane.upgrade(cx) {
1267 // pane.update(cx, |pane, cx| {
1268 // pane.close_item_by_id(
1269 // target_item_id,
1270 // SaveIntent::Close,
1271 // cx,
1272 // )
1273 // .detach_and_log_err(cx);
1274 // })
1275 // }
1276 // }
1277 // }),
1278 // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
1279 // ContextMenuItem::action("Close Clean Items", CloseCleanItems),
1280 // ContextMenuItem::handler("Close Items To The Left", {
1281 // let pane = target_pane.clone();
1282 // move |cx| {
1283 // if let Some(pane) = pane.upgrade(cx) {
1284 // pane.update(cx, |pane, cx| {
1285 // pane.close_items_to_the_left_by_id(target_item_id, cx)
1286 // .detach_and_log_err(cx);
1287 // })
1288 // }
1289 // }
1290 // }),
1291 // ContextMenuItem::handler("Close Items To The Right", {
1292 // let pane = target_pane.clone();
1293 // move |cx| {
1294 // if let Some(pane) = pane.upgrade(cx) {
1295 // pane.update(cx, |pane, cx| {
1296 // pane.close_items_to_the_right_by_id(target_item_id, cx)
1297 // .detach_and_log_err(cx);
1298 // })
1299 // }
1300 // }
1301 // }),
1302 // ContextMenuItem::action(
1303 // "Close All Items",
1304 // CloseAllItems { save_intent: None },
1305 // ),
1306 // ]
1307 // },
1308 // cx,
1309 // );
1310 // });
1311 // }
1312
1313 pub fn toolbar(&self) -> &View<Toolbar> {
1314 &self.toolbar
1315 }
1316
1317 pub fn handle_deleted_project_item(
1318 &mut self,
1319 entry_id: ProjectEntryId,
1320 cx: &mut ViewContext<Pane>,
1321 ) -> Option<()> {
1322 let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
1323 if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
1324 Some((i, item.id()))
1325 } else {
1326 None
1327 }
1328 })?;
1329
1330 self.remove_item(item_index_to_delete, false, cx);
1331 self.nav_history.remove_item(item_id);
1332
1333 Some(())
1334 }
1335
1336 fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
1337 let active_item = self
1338 .items
1339 .get(self.active_item_index)
1340 .map(|item| item.as_ref());
1341 self.toolbar.update(cx, |toolbar, cx| {
1342 toolbar.set_active_item(active_item, cx);
1343 });
1344 }
1345
1346 // fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
1347 // let theme = theme::current(cx).clone();
1348
1349 // let pane = cx.handle().downgrade();
1350 // let autoscroll = if mem::take(&mut self.autoscroll) {
1351 // Some(self.active_item_index)
1352 // } else {
1353 // None
1354 // };
1355
1356 // let pane_active = self.has_focus;
1357
1358 // enum Tabs {}
1359 // let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
1360 // for (ix, (item, detail)) in self
1361 // .items
1362 // .iter()
1363 // .cloned()
1364 // .zip(self.tab_details(cx))
1365 // .enumerate()
1366 // {
1367 // let git_status = item
1368 // .project_path(cx)
1369 // .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
1370 // .and_then(|entry| entry.git_status());
1371
1372 // let detail = if detail == 0 { None } else { Some(detail) };
1373 // let tab_active = ix == self.active_item_index;
1374
1375 // row.add_child({
1376 // enum TabDragReceiver {}
1377 // let mut receiver =
1378 // dragged_item_receiver::<TabDragReceiver, _, _>(self, ix, ix, true, None, cx, {
1379 // let item = item.clone();
1380 // let pane = pane.clone();
1381 // let detail = detail.clone();
1382
1383 // let theme = theme::current(cx).clone();
1384 // let mut tooltip_theme = theme.tooltip.clone();
1385 // tooltip_theme.max_text_width = None;
1386 // let tab_tooltip_text =
1387 // item.tab_tooltip_text(cx).map(|text| text.into_owned());
1388
1389 // let mut tab_style = theme
1390 // .workspace
1391 // .tab_bar
1392 // .tab_style(pane_active, tab_active)
1393 // .clone();
1394 // let should_show_status = settings::get::<ItemSettings>(cx).git_status;
1395 // if should_show_status && git_status != None {
1396 // tab_style.label.text.color = match git_status.unwrap() {
1397 // GitFileStatus::Added => tab_style.git.inserted,
1398 // GitFileStatus::Modified => tab_style.git.modified,
1399 // GitFileStatus::Conflict => tab_style.git.conflict,
1400 // };
1401 // }
1402
1403 // move |mouse_state, cx| {
1404 // let hovered = mouse_state.hovered();
1405
1406 // enum Tab {}
1407 // let mouse_event_handler =
1408 // MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
1409 // Self::render_tab(
1410 // &item,
1411 // pane.clone(),
1412 // ix == 0,
1413 // detail,
1414 // hovered,
1415 // &tab_style,
1416 // cx,
1417 // )
1418 // })
1419 // .on_down(MouseButton::Left, move |_, this, cx| {
1420 // this.activate_item(ix, true, true, cx);
1421 // })
1422 // .on_click(MouseButton::Middle, {
1423 // let item_id = item.id();
1424 // move |_, pane, cx| {
1425 // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1426 // .detach_and_log_err(cx);
1427 // }
1428 // })
1429 // .on_down(
1430 // MouseButton::Right,
1431 // move |event, pane, cx| {
1432 // pane.deploy_tab_context_menu(event.position, item.id(), cx);
1433 // },
1434 // );
1435
1436 // if let Some(tab_tooltip_text) = tab_tooltip_text {
1437 // mouse_event_handler
1438 // .with_tooltip::<Self>(
1439 // ix,
1440 // tab_tooltip_text,
1441 // None,
1442 // tooltip_theme,
1443 // cx,
1444 // )
1445 // .into_any()
1446 // } else {
1447 // mouse_event_handler.into_any()
1448 // }
1449 // }
1450 // });
1451
1452 // if !pane_active || !tab_active {
1453 // receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
1454 // }
1455
1456 // receiver.as_draggable(
1457 // DraggedItem {
1458 // handle: item,
1459 // pane: pane.clone(),
1460 // },
1461 // {
1462 // let theme = theme::current(cx).clone();
1463
1464 // let detail = detail.clone();
1465 // move |_, dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
1466 // let tab_style = &theme.workspace.tab_bar.dragged_tab;
1467 // Self::render_dragged_tab(
1468 // &dragged_item.handle,
1469 // dragged_item.pane.clone(),
1470 // false,
1471 // detail,
1472 // false,
1473 // &tab_style,
1474 // cx,
1475 // )
1476 // }
1477 // },
1478 // )
1479 // })
1480 // }
1481
1482 // // Use the inactive tab style along with the current pane's active status to decide how to render
1483 // // the filler
1484 // let filler_index = self.items.len();
1485 // let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
1486 // enum Filler {}
1487 // row.add_child(
1488 // dragged_item_receiver::<Filler, _, _>(self, 0, filler_index, true, None, cx, |_, _| {
1489 // Empty::new()
1490 // .contained()
1491 // .with_style(filler_style.container)
1492 // .with_border(filler_style.container.border)
1493 // })
1494 // .flex(1., true)
1495 // .into_any_named("filler"),
1496 // );
1497
1498 // row
1499 // }
1500
1501 // fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
1502 // let mut tab_details = (0..self.items.len()).map(|_| 0).collect::<Vec<_>>();
1503
1504 // let mut tab_descriptions = HashMap::default();
1505 // let mut done = false;
1506 // while !done {
1507 // done = true;
1508
1509 // // Store item indices by their tab description.
1510 // for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
1511 // if let Some(description) = item.tab_description(*detail, cx) {
1512 // if *detail == 0
1513 // || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
1514 // {
1515 // tab_descriptions
1516 // .entry(description)
1517 // .or_insert(Vec::new())
1518 // .push(ix);
1519 // }
1520 // }
1521 // }
1522
1523 // // If two or more items have the same tab description, increase their level
1524 // // of detail and try again.
1525 // for (_, item_ixs) in tab_descriptions.drain() {
1526 // if item_ixs.len() > 1 {
1527 // done = false;
1528 // for ix in item_ixs {
1529 // tab_details[ix] += 1;
1530 // }
1531 // }
1532 // }
1533 // }
1534
1535 // tab_details
1536 // }
1537
1538 // fn render_tab(
1539 // item: &Box<dyn ItemHandle>,
1540 // pane: WeakView<Pane>,
1541 // first: bool,
1542 // detail: Option<usize>,
1543 // hovered: bool,
1544 // tab_style: &theme::Tab,
1545 // cx: &mut ViewContext<Self>,
1546 // ) -> AnyElement<Self> {
1547 // let title = item.tab_content(detail, &tab_style, cx);
1548 // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1549 // }
1550
1551 // fn render_dragged_tab(
1552 // item: &Box<dyn ItemHandle>,
1553 // pane: WeakView<Pane>,
1554 // first: bool,
1555 // detail: Option<usize>,
1556 // hovered: bool,
1557 // tab_style: &theme::Tab,
1558 // cx: &mut ViewContext<Workspace>,
1559 // ) -> AnyElement<Workspace> {
1560 // let title = item.dragged_tab_content(detail, &tab_style, cx);
1561 // Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
1562 // }
1563
1564 // fn render_tab_with_title<T: View>(
1565 // title: AnyElement<T>,
1566 // item: &Box<dyn ItemHandle>,
1567 // pane: WeakView<Pane>,
1568 // first: bool,
1569 // hovered: bool,
1570 // tab_style: &theme::Tab,
1571 // cx: &mut ViewContext<T>,
1572 // ) -> AnyElement<T> {
1573 // let mut container = tab_style.container.clone();
1574 // if first {
1575 // container.border.left = false;
1576 // }
1577
1578 // let buffer_jewel_element = {
1579 // let diameter = 7.0;
1580 // let icon_color = if item.has_conflict(cx) {
1581 // Some(tab_style.icon_conflict)
1582 // } else if item.is_dirty(cx) {
1583 // Some(tab_style.icon_dirty)
1584 // } else {
1585 // None
1586 // };
1587
1588 // Canvas::new(move |bounds, _, _, cx| {
1589 // if let Some(color) = icon_color {
1590 // let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
1591 // cx.scene().push_quad(Quad {
1592 // bounds: square,
1593 // background: Some(color),
1594 // border: Default::default(),
1595 // corner_radii: (diameter / 2.).into(),
1596 // });
1597 // }
1598 // })
1599 // .constrained()
1600 // .with_width(diameter)
1601 // .with_height(diameter)
1602 // .aligned()
1603 // };
1604
1605 // let title_element = title.aligned().contained().with_style(ContainerStyle {
1606 // margin: Margin {
1607 // left: tab_style.spacing,
1608 // right: tab_style.spacing,
1609 // ..Default::default()
1610 // },
1611 // ..Default::default()
1612 // });
1613
1614 // let close_element = if hovered {
1615 // let item_id = item.id();
1616 // enum TabCloseButton {}
1617 // let icon = Svg::new("icons/x.svg");
1618 // MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
1619 // if mouse_state.hovered() {
1620 // icon.with_color(tab_style.icon_close_active)
1621 // } else {
1622 // icon.with_color(tab_style.icon_close)
1623 // }
1624 // })
1625 // .with_padding(Padding::uniform(4.))
1626 // .with_cursor_style(CursorStyle::PointingHand)
1627 // .on_click(MouseButton::Left, {
1628 // let pane = pane.clone();
1629 // move |_, _, cx| {
1630 // let pane = pane.clone();
1631 // cx.window_context().defer(move |cx| {
1632 // if let Some(pane) = pane.upgrade(cx) {
1633 // pane.update(cx, |pane, cx| {
1634 // pane.close_item_by_id(item_id, SaveIntent::Close, cx)
1635 // .detach_and_log_err(cx);
1636 // });
1637 // }
1638 // });
1639 // }
1640 // })
1641 // .into_any_named("close-tab-icon")
1642 // .constrained()
1643 // } else {
1644 // Empty::new().constrained()
1645 // }
1646 // .with_width(tab_style.close_icon_width)
1647 // .aligned();
1648
1649 // let close_right = settings::get::<ItemSettings>(cx).close_position.right();
1650
1651 // if close_right {
1652 // Flex::row()
1653 // .with_child(buffer_jewel_element)
1654 // .with_child(title_element)
1655 // .with_child(close_element)
1656 // } else {
1657 // Flex::row()
1658 // .with_child(close_element)
1659 // .with_child(title_element)
1660 // .with_child(buffer_jewel_element)
1661 // }
1662 // .contained()
1663 // .with_style(container)
1664 // .constrained()
1665 // .with_height(tab_style.height)
1666 // .into_any()
1667 // }
1668
1669 // pub fn render_tab_bar_button<
1670 // F1: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1671 // F2: 'static + Fn(&mut Pane, &mut EventContext<Pane>),
1672 // >(
1673 // index: usize,
1674 // icon: &'static str,
1675 // is_active: bool,
1676 // tooltip: Option<(&'static str, Option<Box<dyn Action>>)>,
1677 // cx: &mut ViewContext<Pane>,
1678 // on_click: F1,
1679 // on_down: F2,
1680 // context_menu: Option<ViewHandle<ContextMenu>>,
1681 // ) -> AnyElement<Pane> {
1682 // enum TabBarButton {}
1683
1684 // let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
1685 // let theme = &settings2::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
1686 // let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
1687 // Svg::new(icon)
1688 // .with_color(style.color)
1689 // .constrained()
1690 // .with_width(style.icon_width)
1691 // .aligned()
1692 // .constrained()
1693 // .with_width(style.button_width)
1694 // .with_height(style.button_width)
1695 // })
1696 // .with_cursor_style(CursorStyle::PointingHand)
1697 // .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx))
1698 // .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
1699 // .into_any();
1700 // if let Some((tooltip, action)) = tooltip {
1701 // let tooltip_style = settings::get::<ThemeSettings>(cx).theme.tooltip.clone();
1702 // button = button
1703 // .with_tooltip::<TabBarButton>(index, tooltip, action, tooltip_style, cx)
1704 // .into_any();
1705 // }
1706
1707 // Stack::new()
1708 // .with_child(button)
1709 // .with_children(
1710 // context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
1711 // )
1712 // .flex(1., false)
1713 // .into_any_named("tab bar button")
1714 // }
1715
1716 // fn render_blank_pane(&self, theme: &Theme, _cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1717 // let background = theme.workspace.background;
1718 // Empty::new()
1719 // .contained()
1720 // .with_background_color(background)
1721 // .into_any()
1722 // }
1723
1724 // pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1725 // self.zoomed = zoomed;
1726 // cx.notify();
1727 // }
1728
1729 // pub fn is_zoomed(&self) -> bool {
1730 // self.zoomed
1731 // }
1732}
1733
1734// impl Entity for Pane {
1735// type Event = Event;
1736// }
1737
1738// impl View for Pane {
1739// fn ui_name() -> &'static str {
1740// "Pane"
1741// }
1742
1743// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
1744// enum MouseNavigationHandler {}
1745
1746// MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
1747// let active_item_index = self.active_item_index;
1748
1749// if let Some(active_item) = self.active_item() {
1750// Flex::column()
1751// .with_child({
1752// let theme = theme::current(cx).clone();
1753
1754// let mut stack = Stack::new();
1755
1756// enum TabBarEventHandler {}
1757// stack.add_child(
1758// MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
1759// Empty::new()
1760// .contained()
1761// .with_style(theme.workspace.tab_bar.container)
1762// })
1763// .on_down(
1764// MouseButton::Left,
1765// move |_, this, cx| {
1766// this.activate_item(active_item_index, true, true, cx);
1767// },
1768// ),
1769// );
1770// let tooltip_style = theme.tooltip.clone();
1771// let tab_bar_theme = theme.workspace.tab_bar.clone();
1772
1773// let nav_button_height = tab_bar_theme.height;
1774// let button_style = tab_bar_theme.nav_button;
1775// let border_for_nav_buttons = tab_bar_theme
1776// .tab_style(false, false)
1777// .container
1778// .border
1779// .clone();
1780
1781// let mut tab_row = Flex::row()
1782// .with_child(nav_button(
1783// "icons/arrow_left.svg",
1784// button_style.clone(),
1785// nav_button_height,
1786// tooltip_style.clone(),
1787// self.can_navigate_backward(),
1788// {
1789// move |pane, cx| {
1790// if let Some(workspace) = pane.workspace.upgrade(cx) {
1791// let pane = cx.weak_handle();
1792// cx.window_context().defer(move |cx| {
1793// workspace.update(cx, |workspace, cx| {
1794// workspace
1795// .go_back(pane, cx)
1796// .detach_and_log_err(cx)
1797// })
1798// })
1799// }
1800// }
1801// },
1802// super::GoBack,
1803// "Go Back",
1804// cx,
1805// ))
1806// .with_child(
1807// nav_button(
1808// "icons/arrow_right.svg",
1809// button_style.clone(),
1810// nav_button_height,
1811// tooltip_style,
1812// self.can_navigate_forward(),
1813// {
1814// move |pane, cx| {
1815// if let Some(workspace) = pane.workspace.upgrade(cx) {
1816// let pane = cx.weak_handle();
1817// cx.window_context().defer(move |cx| {
1818// workspace.update(cx, |workspace, cx| {
1819// workspace
1820// .go_forward(pane, cx)
1821// .detach_and_log_err(cx)
1822// })
1823// })
1824// }
1825// }
1826// },
1827// super::GoForward,
1828// "Go Forward",
1829// cx,
1830// )
1831// .contained()
1832// .with_border(border_for_nav_buttons),
1833// )
1834// .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs"));
1835
1836// if self.has_focus {
1837// let render_tab_bar_buttons = self.render_tab_bar_buttons.clone();
1838// tab_row.add_child(
1839// (render_tab_bar_buttons)(self, cx)
1840// .contained()
1841// .with_style(theme.workspace.tab_bar.pane_button_container)
1842// .flex(1., false)
1843// .into_any(),
1844// )
1845// }
1846
1847// stack.add_child(tab_row);
1848// stack
1849// .constrained()
1850// .with_height(theme.workspace.tab_bar.height)
1851// .flex(1., false)
1852// .into_any_named("tab bar")
1853// })
1854// .with_child({
1855// enum PaneContentTabDropTarget {}
1856// dragged_item_receiver::<PaneContentTabDropTarget, _, _>(
1857// self,
1858// 0,
1859// self.active_item_index + 1,
1860// !self.can_split,
1861// if self.can_split { Some(100.) } else { None },
1862// cx,
1863// {
1864// let toolbar = self.toolbar.clone();
1865// let toolbar_hidden = toolbar.read(cx).hidden();
1866// move |_, cx| {
1867// Flex::column()
1868// .with_children(
1869// (!toolbar_hidden)
1870// .then(|| ChildView::new(&toolbar, cx).expanded()),
1871// )
1872// .with_child(
1873// ChildView::new(active_item.as_any(), cx).flex(1., true),
1874// )
1875// }
1876// },
1877// )
1878// .flex(1., true)
1879// })
1880// .with_child(ChildView::new(&self.tab_context_menu, cx))
1881// .into_any()
1882// } else {
1883// enum EmptyPane {}
1884// let theme = theme::current(cx).clone();
1885
1886// dragged_item_receiver::<EmptyPane, _, _>(self, 0, 0, false, None, cx, |_, cx| {
1887// self.render_blank_pane(&theme, cx)
1888// })
1889// .on_down(MouseButton::Left, |_, _, cx| {
1890// cx.focus_parent();
1891// })
1892// .into_any()
1893// }
1894// })
1895// .on_down(
1896// MouseButton::Navigate(NavigationDirection::Back),
1897// move |_, pane, cx| {
1898// if let Some(workspace) = pane.workspace.upgrade(cx) {
1899// let pane = cx.weak_handle();
1900// cx.window_context().defer(move |cx| {
1901// workspace.update(cx, |workspace, cx| {
1902// workspace.go_back(pane, cx).detach_and_log_err(cx)
1903// })
1904// })
1905// }
1906// },
1907// )
1908// .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
1909// move |_, pane, cx| {
1910// if let Some(workspace) = pane.workspace.upgrade(cx) {
1911// let pane = cx.weak_handle();
1912// cx.window_context().defer(move |cx| {
1913// workspace.update(cx, |workspace, cx| {
1914// workspace.go_forward(pane, cx).detach_and_log_err(cx)
1915// })
1916// })
1917// }
1918// }
1919// })
1920// .into_any_named("pane")
1921// }
1922
1923// fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
1924// if !self.has_focus {
1925// self.has_focus = true;
1926// cx.emit(Event::Focus);
1927// cx.notify();
1928// }
1929
1930// self.toolbar.update(cx, |toolbar, cx| {
1931// toolbar.focus_changed(true, cx);
1932// });
1933
1934// if let Some(active_item) = self.active_item() {
1935// if cx.is_self_focused() {
1936// // Pane was focused directly. We need to either focus a view inside the active item,
1937// // or focus the active item itself
1938// if let Some(weak_last_focused_view) =
1939// self.last_focused_view_by_item.get(&active_item.id())
1940// {
1941// if let Some(last_focused_view) = weak_last_focused_view.upgrade(cx) {
1942// cx.focus(&last_focused_view);
1943// return;
1944// } else {
1945// self.last_focused_view_by_item.remove(&active_item.id());
1946// }
1947// }
1948
1949// cx.focus(active_item.as_any());
1950// } else if focused != self.tab_bar_context_menu.handle {
1951// self.last_focused_view_by_item
1952// .insert(active_item.id(), focused.downgrade());
1953// }
1954// }
1955// }
1956
1957// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
1958// self.has_focus = false;
1959// self.toolbar.update(cx, |toolbar, cx| {
1960// toolbar.focus_changed(false, cx);
1961// });
1962// cx.notify();
1963// }
1964
1965// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
1966// Self::reset_to_default_keymap_context(keymap);
1967// }
1968// }
1969
1970impl ItemNavHistory {
1971 pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
1972 self.history.push(data, self.item.clone(), cx);
1973 }
1974
1975 pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1976 self.history.pop(NavigationMode::GoingBack, cx)
1977 }
1978
1979 pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
1980 self.history.pop(NavigationMode::GoingForward, cx)
1981 }
1982}
1983
1984impl NavHistory {
1985 pub fn for_each_entry(
1986 &self,
1987 cx: &AppContext,
1988 mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
1989 ) {
1990 let borrowed_history = self.0.lock();
1991 borrowed_history
1992 .forward_stack
1993 .iter()
1994 .chain(borrowed_history.backward_stack.iter())
1995 .chain(borrowed_history.closed_stack.iter())
1996 .for_each(|entry| {
1997 if let Some(project_and_abs_path) =
1998 borrowed_history.paths_by_item.get(&entry.item.id())
1999 {
2000 f(entry, project_and_abs_path.clone());
2001 } else if let Some(item) = entry.item.upgrade() {
2002 if let Some(path) = item.project_path(cx) {
2003 f(entry, (path, None));
2004 }
2005 }
2006 })
2007 }
2008
2009 pub fn set_mode(&mut self, mode: NavigationMode) {
2010 self.0.lock().mode = mode;
2011 }
2012
2013 pub fn mode(&self) -> NavigationMode {
2014 self.0.lock().mode
2015 }
2016
2017 pub fn disable(&mut self) {
2018 self.0.lock().mode = NavigationMode::Disabled;
2019 }
2020
2021 pub fn enable(&mut self) {
2022 self.0.lock().mode = NavigationMode::Normal;
2023 }
2024
2025 pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
2026 let mut state = self.0.lock();
2027 let entry = match mode {
2028 NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
2029 return None
2030 }
2031 NavigationMode::GoingBack => &mut state.backward_stack,
2032 NavigationMode::GoingForward => &mut state.forward_stack,
2033 NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
2034 }
2035 .pop_back();
2036 if entry.is_some() {
2037 state.did_update(cx);
2038 }
2039 entry
2040 }
2041
2042 pub fn push<D: 'static + Send + Any>(
2043 &mut self,
2044 data: Option<D>,
2045 item: Arc<dyn WeakItemHandle>,
2046 cx: &mut WindowContext,
2047 ) {
2048 let state = &mut *self.0.lock();
2049 match state.mode {
2050 NavigationMode::Disabled => {}
2051 NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
2052 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2053 state.backward_stack.pop_front();
2054 }
2055 state.backward_stack.push_back(NavigationEntry {
2056 item,
2057 data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2058 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2059 });
2060 state.forward_stack.clear();
2061 }
2062 NavigationMode::GoingBack => {
2063 if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2064 state.forward_stack.pop_front();
2065 }
2066 state.forward_stack.push_back(NavigationEntry {
2067 item,
2068 data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2069 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2070 });
2071 }
2072 NavigationMode::GoingForward => {
2073 if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2074 state.backward_stack.pop_front();
2075 }
2076 state.backward_stack.push_back(NavigationEntry {
2077 item,
2078 data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2079 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2080 });
2081 }
2082 NavigationMode::ClosingItem => {
2083 if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
2084 state.closed_stack.pop_front();
2085 }
2086 state.closed_stack.push_back(NavigationEntry {
2087 item,
2088 data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
2089 timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
2090 });
2091 }
2092 }
2093 state.did_update(cx);
2094 }
2095
2096 pub fn remove_item(&mut self, item_id: usize) {
2097 let mut state = self.0.lock();
2098 state.paths_by_item.remove(&item_id);
2099 state
2100 .backward_stack
2101 .retain(|entry| entry.item.id() != item_id);
2102 state
2103 .forward_stack
2104 .retain(|entry| entry.item.id() != item_id);
2105 state
2106 .closed_stack
2107 .retain(|entry| entry.item.id() != item_id);
2108 }
2109
2110 pub fn path_for_item(&self, item_id: usize) -> Option<(ProjectPath, Option<PathBuf>)> {
2111 self.0.lock().paths_by_item.get(&item_id).cloned()
2112 }
2113}
2114
2115impl NavHistoryState {
2116 pub fn did_update(&self, cx: &mut WindowContext) {
2117 if let Some(pane) = self.pane.upgrade() {
2118 cx.defer(move |cx| {
2119 pane.update(cx, |pane, cx| pane.history_updated(cx));
2120 });
2121 }
2122 }
2123}
2124
2125// pub struct PaneBackdrop<V> {
2126// child_view: usize,
2127// child: AnyElement<V>,
2128// }
2129
2130// impl<V> PaneBackdrop<V> {
2131// pub fn new(pane_item_view: usize, child: AnyElement<V>) -> Self {
2132// PaneBackdrop {
2133// child,
2134// child_view: pane_item_view,
2135// }
2136// }
2137// }
2138
2139// impl<V: 'static> Element<V> for PaneBackdrop<V> {
2140// type LayoutState = ();
2141
2142// type PaintState = ();
2143
2144// fn layout(
2145// &mut self,
2146// constraint: gpui::SizeConstraint,
2147// view: &mut V,
2148// cx: &mut ViewContext<V>,
2149// ) -> (Vector2F, Self::LayoutState) {
2150// let size = self.child.layout(constraint, view, cx);
2151// (size, ())
2152// }
2153
2154// fn paint(
2155// &mut self,
2156// bounds: RectF,
2157// visible_bounds: RectF,
2158// _: &mut Self::LayoutState,
2159// view: &mut V,
2160// cx: &mut ViewContext<V>,
2161// ) -> Self::PaintState {
2162// let background = theme::current(cx).editor.background;
2163
2164// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
2165
2166// cx.scene().push_quad(gpui::Quad {
2167// bounds: RectF::new(bounds.origin(), bounds.size()),
2168// background: Some(background),
2169// ..Default::default()
2170// });
2171
2172// let child_view_id = self.child_view;
2173// cx.scene().push_mouse_region(
2174// MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
2175// gpui::platform::MouseButton::Left,
2176// move |_, _: &mut V, cx| {
2177// let window = cx.window();
2178// cx.app_context().focus(window, Some(child_view_id))
2179// },
2180// ),
2181// );
2182
2183// cx.scene().push_layer(Some(bounds));
2184// self.child.paint(bounds.origin(), visible_bounds, view, cx);
2185// cx.scene().pop_layer();
2186// }
2187
2188// fn rect_for_text_range(
2189// &self,
2190// range_utf16: std::ops::Range<usize>,
2191// _bounds: RectF,
2192// _visible_bounds: RectF,
2193// _layout: &Self::LayoutState,
2194// _paint: &Self::PaintState,
2195// view: &V,
2196// cx: &gpui::ViewContext<V>,
2197// ) -> Option<RectF> {
2198// self.child.rect_for_text_range(range_utf16, view, cx)
2199// }
2200
2201// fn debug(
2202// &self,
2203// _bounds: RectF,
2204// _layout: &Self::LayoutState,
2205// _paint: &Self::PaintState,
2206// view: &V,
2207// cx: &gpui::ViewContext<V>,
2208// ) -> serde_json::Value {
2209// gpui::json::json!({
2210// "type": "Pane Back Drop",
2211// "view": self.child_view,
2212// "child": self.child.debug(view, cx),
2213// })
2214// }
2215// }
2216
2217// fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
2218// let path = buffer_path
2219// .as_ref()
2220// .and_then(|p| p.path.to_str())
2221// .unwrap_or(&"This buffer");
2222// let path = truncate_and_remove_front(path, 80);
2223// format!("{path} contains unsaved edits. Do you want to save it?")
2224// }
2225
2226// todo!("uncomment tests")
2227// #[cfg(test)]
2228// mod tests {
2229// use super::*;
2230// use crate::item::test::{TestItem, TestProjectItem};
2231// use gpui::TestAppContext;
2232// use project::FakeFs;
2233// use settings::SettingsStore;
2234
2235// #[gpui::test]
2236// async fn test_remove_active_empty(cx: &mut TestAppContext) {
2237// init_test(cx);
2238// let fs = FakeFs::new(cx.background());
2239
2240// let project = Project::test(fs, None, cx).await;
2241// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2242// let workspace = window.root(cx);
2243// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2244
2245// pane.update(cx, |pane, cx| {
2246// assert!(pane
2247// .close_active_item(&CloseActiveItem { save_intent: None }, cx)
2248// .is_none())
2249// });
2250// }
2251
2252// #[gpui::test]
2253// async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
2254// cx.foreground().forbid_parking();
2255// init_test(cx);
2256// let fs = FakeFs::new(cx.background());
2257
2258// let project = Project::test(fs, None, cx).await;
2259// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2260// let workspace = window.root(cx);
2261// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2262
2263// // 1. Add with a destination index
2264// // a. Add before the active item
2265// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2266// pane.update(cx, |pane, cx| {
2267// pane.add_item(
2268// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2269// false,
2270// false,
2271// Some(0),
2272// cx,
2273// );
2274// });
2275// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2276
2277// // b. Add after the active item
2278// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2279// pane.update(cx, |pane, cx| {
2280// pane.add_item(
2281// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2282// false,
2283// false,
2284// Some(2),
2285// cx,
2286// );
2287// });
2288// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2289
2290// // c. Add at the end of the item list (including off the length)
2291// set_labeled_items(&pane, ["A", "B*", "C"], cx);
2292// pane.update(cx, |pane, cx| {
2293// pane.add_item(
2294// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2295// false,
2296// false,
2297// Some(5),
2298// cx,
2299// );
2300// });
2301// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2302
2303// // 2. Add without a destination index
2304// // a. Add with active item at the start of the item list
2305// set_labeled_items(&pane, ["A*", "B", "C"], cx);
2306// pane.update(cx, |pane, cx| {
2307// pane.add_item(
2308// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2309// false,
2310// false,
2311// None,
2312// cx,
2313// );
2314// });
2315// set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
2316
2317// // b. Add with active item at the end of the item list
2318// set_labeled_items(&pane, ["A", "B", "C*"], cx);
2319// pane.update(cx, |pane, cx| {
2320// pane.add_item(
2321// Box::new(cx.add_view(|_| TestItem::new().with_label("D"))),
2322// false,
2323// false,
2324// None,
2325// cx,
2326// );
2327// });
2328// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2329// }
2330
2331// #[gpui::test]
2332// async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
2333// cx.foreground().forbid_parking();
2334// init_test(cx);
2335// let fs = FakeFs::new(cx.background());
2336
2337// let project = Project::test(fs, None, cx).await;
2338// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2339// let workspace = window.root(cx);
2340// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2341
2342// // 1. Add with a destination index
2343// // 1a. Add before the active item
2344// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2345// pane.update(cx, |pane, cx| {
2346// pane.add_item(d, false, false, Some(0), cx);
2347// });
2348// assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
2349
2350// // 1b. Add after the active item
2351// let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2352// pane.update(cx, |pane, cx| {
2353// pane.add_item(d, false, false, Some(2), cx);
2354// });
2355// assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
2356
2357// // 1c. Add at the end of the item list (including off the length)
2358// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
2359// pane.update(cx, |pane, cx| {
2360// pane.add_item(a, false, false, Some(5), cx);
2361// });
2362// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2363
2364// // 1d. Add same item to active index
2365// let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2366// pane.update(cx, |pane, cx| {
2367// pane.add_item(b, false, false, Some(1), cx);
2368// });
2369// assert_item_labels(&pane, ["A", "B*", "C"], cx);
2370
2371// // 1e. Add item to index after same item in last position
2372// let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
2373// pane.update(cx, |pane, cx| {
2374// pane.add_item(c, false, false, Some(2), cx);
2375// });
2376// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2377
2378// // 2. Add without a destination index
2379// // 2a. Add with active item at the start of the item list
2380// let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
2381// pane.update(cx, |pane, cx| {
2382// pane.add_item(d, false, false, None, cx);
2383// });
2384// assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
2385
2386// // 2b. Add with active item at the end of the item list
2387// let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
2388// pane.update(cx, |pane, cx| {
2389// pane.add_item(a, false, false, None, cx);
2390// });
2391// assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
2392
2393// // 2c. Add active item to active item at end of list
2394// let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
2395// pane.update(cx, |pane, cx| {
2396// pane.add_item(c, false, false, None, cx);
2397// });
2398// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2399
2400// // 2d. Add active item to active item at start of list
2401// let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
2402// pane.update(cx, |pane, cx| {
2403// pane.add_item(a, false, false, None, cx);
2404// });
2405// assert_item_labels(&pane, ["A*", "B", "C"], cx);
2406// }
2407
2408// #[gpui::test]
2409// async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
2410// cx.foreground().forbid_parking();
2411// init_test(cx);
2412// let fs = FakeFs::new(cx.background());
2413
2414// let project = Project::test(fs, None, cx).await;
2415// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2416// let workspace = window.root(cx);
2417// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2418
2419// // singleton view
2420// pane.update(cx, |pane, cx| {
2421// let item = TestItem::new()
2422// .with_singleton(true)
2423// .with_label("buffer 1")
2424// .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]);
2425
2426// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2427// });
2428// assert_item_labels(&pane, ["buffer 1*"], cx);
2429
2430// // new singleton view with the same project entry
2431// pane.update(cx, |pane, cx| {
2432// let item = TestItem::new()
2433// .with_singleton(true)
2434// .with_label("buffer 1")
2435// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2436
2437// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2438// });
2439// assert_item_labels(&pane, ["buffer 1*"], cx);
2440
2441// // new singleton view with different project entry
2442// pane.update(cx, |pane, cx| {
2443// let item = TestItem::new()
2444// .with_singleton(true)
2445// .with_label("buffer 2")
2446// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]);
2447// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2448// });
2449// assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
2450
2451// // new multibuffer view with the same project entry
2452// pane.update(cx, |pane, cx| {
2453// let item = TestItem::new()
2454// .with_singleton(false)
2455// .with_label("multibuffer 1")
2456// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2457
2458// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2459// });
2460// assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
2461
2462// // another multibuffer view with the same project entry
2463// pane.update(cx, |pane, cx| {
2464// let item = TestItem::new()
2465// .with_singleton(false)
2466// .with_label("multibuffer 1b")
2467// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]);
2468
2469// pane.add_item(Box::new(cx.add_view(|_| item)), false, false, None, cx);
2470// });
2471// assert_item_labels(
2472// &pane,
2473// ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
2474// cx,
2475// );
2476// }
2477
2478// #[gpui::test]
2479// async fn test_remove_item_ordering(cx: &mut TestAppContext) {
2480// init_test(cx);
2481// let fs = FakeFs::new(cx.background());
2482
2483// let project = Project::test(fs, None, cx).await;
2484// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2485// let workspace = window.root(cx);
2486// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2487
2488// add_labeled_item(&pane, "A", false, cx);
2489// add_labeled_item(&pane, "B", false, cx);
2490// add_labeled_item(&pane, "C", false, cx);
2491// add_labeled_item(&pane, "D", false, cx);
2492// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2493
2494// pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
2495// add_labeled_item(&pane, "1", false, cx);
2496// assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
2497
2498// pane.update(cx, |pane, cx| {
2499// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2500// })
2501// .unwrap()
2502// .await
2503// .unwrap();
2504// assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
2505
2506// pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
2507// assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
2508
2509// pane.update(cx, |pane, cx| {
2510// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2511// })
2512// .unwrap()
2513// .await
2514// .unwrap();
2515// assert_item_labels(&pane, ["A", "B*", "C"], cx);
2516
2517// pane.update(cx, |pane, cx| {
2518// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2519// })
2520// .unwrap()
2521// .await
2522// .unwrap();
2523// assert_item_labels(&pane, ["A", "C*"], cx);
2524
2525// pane.update(cx, |pane, cx| {
2526// pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
2527// })
2528// .unwrap()
2529// .await
2530// .unwrap();
2531// assert_item_labels(&pane, ["A*"], cx);
2532// }
2533
2534// #[gpui::test]
2535// async fn test_close_inactive_items(cx: &mut TestAppContext) {
2536// init_test(cx);
2537// let fs = FakeFs::new(cx.background());
2538
2539// let project = Project::test(fs, None, cx).await;
2540// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2541// let workspace = window.root(cx);
2542// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2543
2544// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2545
2546// pane.update(cx, |pane, cx| {
2547// pane.close_inactive_items(&CloseInactiveItems, cx)
2548// })
2549// .unwrap()
2550// .await
2551// .unwrap();
2552// assert_item_labels(&pane, ["C*"], cx);
2553// }
2554
2555// #[gpui::test]
2556// async fn test_close_clean_items(cx: &mut TestAppContext) {
2557// init_test(cx);
2558// let fs = FakeFs::new(cx.background());
2559
2560// let project = Project::test(fs, None, cx).await;
2561// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2562// let workspace = window.root(cx);
2563// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2564
2565// add_labeled_item(&pane, "A", true, cx);
2566// add_labeled_item(&pane, "B", false, cx);
2567// add_labeled_item(&pane, "C", true, cx);
2568// add_labeled_item(&pane, "D", false, cx);
2569// add_labeled_item(&pane, "E", false, cx);
2570// assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
2571
2572// pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
2573// .unwrap()
2574// .await
2575// .unwrap();
2576// assert_item_labels(&pane, ["A^", "C*^"], cx);
2577// }
2578
2579// #[gpui::test]
2580// async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
2581// init_test(cx);
2582// let fs = FakeFs::new(cx.background());
2583
2584// let project = Project::test(fs, None, cx).await;
2585// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2586// let workspace = window.root(cx);
2587// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2588
2589// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2590
2591// pane.update(cx, |pane, cx| {
2592// pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
2593// })
2594// .unwrap()
2595// .await
2596// .unwrap();
2597// assert_item_labels(&pane, ["C*", "D", "E"], cx);
2598// }
2599
2600// #[gpui::test]
2601// async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
2602// init_test(cx);
2603// let fs = FakeFs::new(cx.background());
2604
2605// let project = Project::test(fs, None, cx).await;
2606// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2607// let workspace = window.root(cx);
2608// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2609
2610// set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
2611
2612// pane.update(cx, |pane, cx| {
2613// pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
2614// })
2615// .unwrap()
2616// .await
2617// .unwrap();
2618// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2619// }
2620
2621// #[gpui::test]
2622// async fn test_close_all_items(cx: &mut TestAppContext) {
2623// init_test(cx);
2624// let fs = FakeFs::new(cx.background());
2625
2626// let project = Project::test(fs, None, cx).await;
2627// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2628// let workspace = window.root(cx);
2629// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2630
2631// add_labeled_item(&pane, "A", false, cx);
2632// add_labeled_item(&pane, "B", false, cx);
2633// add_labeled_item(&pane, "C", false, cx);
2634// assert_item_labels(&pane, ["A", "B", "C*"], cx);
2635
2636// pane.update(cx, |pane, cx| {
2637// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2638// })
2639// .unwrap()
2640// .await
2641// .unwrap();
2642// assert_item_labels(&pane, [], cx);
2643
2644// add_labeled_item(&pane, "A", true, cx);
2645// add_labeled_item(&pane, "B", true, cx);
2646// add_labeled_item(&pane, "C", true, cx);
2647// assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
2648
2649// let save = pane
2650// .update(cx, |pane, cx| {
2651// pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
2652// })
2653// .unwrap();
2654
2655// cx.foreground().run_until_parked();
2656// window.simulate_prompt_answer(2, cx);
2657// save.await.unwrap();
2658// assert_item_labels(&pane, [], cx);
2659// }
2660
2661// fn init_test(cx: &mut TestAppContext) {
2662// cx.update(|cx| {
2663// cx.set_global(SettingsStore::test(cx));
2664// theme::init((), cx);
2665// crate::init_settings(cx);
2666// Project::init_settings(cx);
2667// });
2668// }
2669
2670// fn add_labeled_item(
2671// pane: &ViewHandle<Pane>,
2672// label: &str,
2673// is_dirty: bool,
2674// cx: &mut TestAppContext,
2675// ) -> Box<ViewHandle<TestItem>> {
2676// pane.update(cx, |pane, cx| {
2677// let labeled_item =
2678// Box::new(cx.add_view(|_| TestItem::new().with_label(label).with_dirty(is_dirty)));
2679// pane.add_item(labeled_item.clone(), false, false, None, cx);
2680// labeled_item
2681// })
2682// }
2683
2684// fn set_labeled_items<const COUNT: usize>(
2685// pane: &ViewHandle<Pane>,
2686// labels: [&str; COUNT],
2687// cx: &mut TestAppContext,
2688// ) -> [Box<ViewHandle<TestItem>>; COUNT] {
2689// pane.update(cx, |pane, cx| {
2690// pane.items.clear();
2691// let mut active_item_index = 0;
2692
2693// let mut index = 0;
2694// let items = labels.map(|mut label| {
2695// if label.ends_with("*") {
2696// label = label.trim_end_matches("*");
2697// active_item_index = index;
2698// }
2699
2700// let labeled_item = Box::new(cx.add_view(|_| TestItem::new().with_label(label)));
2701// pane.add_item(labeled_item.clone(), false, false, None, cx);
2702// index += 1;
2703// labeled_item
2704// });
2705
2706// pane.activate_item(active_item_index, false, false, cx);
2707
2708// items
2709// })
2710// }
2711
2712// // Assert the item label, with the active item label suffixed with a '*'
2713// fn assert_item_labels<const COUNT: usize>(
2714// pane: &ViewHandle<Pane>,
2715// expected_states: [&str; COUNT],
2716// cx: &mut TestAppContext,
2717// ) {
2718// pane.read_with(cx, |pane, cx| {
2719// let actual_states = pane
2720// .items
2721// .iter()
2722// .enumerate()
2723// .map(|(ix, item)| {
2724// let mut state = item
2725// .as_any()
2726// .downcast_ref::<TestItem>()
2727// .unwrap()
2728// .read(cx)
2729// .label
2730// .clone();
2731// if ix == pane.active_item_index {
2732// state.push('*');
2733// }
2734// if item.is_dirty(cx) {
2735// state.push('^');
2736// }
2737// state
2738// })
2739// .collect::<Vec<_>>();
2740
2741// assert_eq!(
2742// actual_states, expected_states,
2743// "pane items do not match expectation"
2744// );
2745// })
2746// }
2747// }