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